寫在前面
如果你是iOS開發(fā)者,并且在處理NSString字符上遇到了一些問(wèn)題栅迄,強(qiáng)烈建議去看看Objc中國(guó)上關(guān)于 NSString 與 Unicode站故。
簡(jiǎn)介
Unicode對(duì)世界上大部分的文字系統(tǒng)進(jìn)行了整理、編碼毅舆,使得電腦可以用更為簡(jiǎn)單的方式來(lái)呈現(xiàn)和處理文字西篓。這是維基百科對(duì)Unicode下的定義。
Unicode的實(shí)現(xiàn)方式包含了UTF-8憋活、UTF-16(字符用兩個(gè)字節(jié)或者四個(gè)字節(jié)表示)和UTF-32(用四個(gè)字節(jié)來(lái)表示)岂津,下面對(duì)面一一進(jìn)行介紹。
UTF-8
UTF-8的最明顯的一個(gè)特點(diǎn)是它是變長(zhǎng)的悦即,它可以使用1到4個(gè)字節(jié)表示一個(gè)符號(hào)吮成,根據(jù)不同的符號(hào)變化字節(jié)長(zhǎng)度。
先把阮一峰在《字符編碼筆記:ASCII辜梳,Unicode和UTF-8》中對(duì)UTF-8的編寫規(guī)則的一個(gè)總結(jié)放出來(lái)粱甫。
??????UTF-8的編碼規(guī)則很簡(jiǎn)單,只有二條:
1作瞄、對(duì)于單字節(jié)的符號(hào)茶宵,字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號(hào)的unicode碼粉洼。因此對(duì)于英語(yǔ)字母节预,UTF-8編碼和ASCII碼是相同的叶摄。
2属韧、對(duì)于n字節(jié)的符號(hào)(n>1),第一個(gè)字節(jié)的前n位都設(shè)為1蛤吓,第n+1位設(shè)為0宵喂,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進(jìn)制位会傲,全部為這個(gè)符號(hào)的unicode碼锅棕。
提出問(wèn)題:第一個(gè)字節(jié)前面n位設(shè)1是為了知道當(dāng)前字符占用多少字節(jié),而后面字節(jié)的前兩位字節(jié)為什么要設(shè)置為10呢淌山?下面會(huì)馬上進(jìn)行解釋裸燎。
現(xiàn)在我們來(lái)具體的分析一下Unicode的不同范圍:下面中前兩個(gè)描述是否為ASCII,后兩個(gè)描述多字節(jié)序列
-
U+0000到U+007F(ASCII)
從U+0000到U+007F被編碼為0x00~0x7F的單字節(jié)泼疑,這是ASCII碼的所有字符德绿,一共128個(gè)字符,所以Unicode是完全用來(lái)容納ASCII的。回答上面提出的問(wèn)題:后面字節(jié)的前兩位一律設(shè)為10(
10000000
也就是80)是因?yàn)楸仨氁笥?F才和ASCII碼分開移稳。 大于 U+007F(非 ASCII)
所有大于 U+007F 的字符被編碼為一串多字節(jié)序列蕴纳,這樣就可以區(qū)分一串多字節(jié)序列是多字節(jié)碼還是 ASCII 碼。0xFE 和 0xFF 不會(huì)被用于 UTF-8 編碼中个粱。
多字節(jié)序列的第一個(gè)字節(jié)在0xC00xFD中古毛,剩余字節(jié)在0x800xBF內(nèi)。
這里解釋一下為什么第一個(gè)是在0xC0~0xFD中都许,理解這里需要再回去看看上面注意中提到的Unicode編碼規(guī)則稻薇。因?yàn)楸硎镜氖嵌嘧止?jié)就表明n是大于1的,所以第一個(gè)字節(jié)最小的值為:11000000即C0
(表明當(dāng)前有兩個(gè)字節(jié)胶征。每四位表示一個(gè)十六進(jìn)制數(shù)颖低,這也是為什么在編程的時(shí)候喜歡用十六進(jìn)制數(shù)的原因),如果在沒有限制的情況下弧烤,通過(guò)上面的結(jié)論我們可以得到第一個(gè)字節(jié)能表示的最大的數(shù)是0xFE(11111110)忱屑,就是前面7位設(shè)置1最后一位設(shè)置為0,但是上面一條中提到不包含F(xiàn)E暇昂,所以第一個(gè)字節(jié)的最大值為0xFD(11111101)
莺戒。
同理因?yàn)?code>后面字節(jié)的前兩位一律設(shè)為10所以多字節(jié)除了第一個(gè)字節(jié)的其他字節(jié)最大值為10111111(BF)
。
總結(jié)
UTF-8 編碼字符最長(zhǎng)可達(dá)六個(gè)字節(jié)
Unicode 字符: UTF-8 碼:
U-00000000 - U-0000007F: 0xxxxxxx ///表示ASCII
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx ///
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
舉個(gè)例子:漢字“王”的Unicode碼為U+73B8
轉(zhuǎn)換為二進(jìn)制為:0111 0011 1010 1000
急波,"73b8"位于上面的第三類从铲,把轉(zhuǎn)換的16個(gè)二進(jìn)制依次放入(一定是一次放入不管空格的)上訴的x中:
11100111 10001110 10101000
/// 同樣我們來(lái)驗(yàn)證一下阮一峰舉例的“嚴(yán)”字
/// 4E25 同樣屬于上訴的第三類 ,對(duì)應(yīng)的二進(jìn)制 => 0100 1110 0010 0101
/// 11100100 10111000 10100101 得到的結(jié)果和他文章中的一樣澄暮。
到這里我自己覺得應(yīng)該是把UTF-8的編碼方式說(shuō)清楚了名段,最后再來(lái)一個(gè)編碼的順序(很適合于我的方式)
編碼的順序
對(duì)于單字節(jié):
直接將其轉(zhuǎn)換為八位的二進(jìn)制就可以了;
對(duì)于多字節(jié):
- 1.找到Unicode碼對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)
- 2.查看該Unicode碼在分類中屬于第幾類
- 3.一次填入二進(jìn)制碼
UTF-16
UTF-16是Unicode字符編碼五層次模型的第三層:字符編碼表(Character Encoding Form泣懊,也稱為"storage format")的一種實(shí)現(xiàn)方式伸辟。即把Unicode字符集的抽象碼位映射為16位長(zhǎng)的整數(shù)(即碼元)的序列,用于數(shù)據(jù)存儲(chǔ)或傳遞馍刮。
這里需要說(shuō)明一下基本多文種平面-BMP和輔助平面-SMP信夫,在維基百科中每一個(gè)平面相關(guān)的圖片下面都說(shuō)了"每個(gè)寫著數(shù)字的格子代表256個(gè)碼點(diǎn)",即00~FF卡啰。例如:位于BMP中00
格子中的一個(gè)碼點(diǎn)表示為:0x00E5
静稻。
下面同樣用一下阮一峰的規(guī)則總結(jié):
基本平面的字符占用2個(gè)字節(jié),輔助平面的字符占用4個(gè)字節(jié)匈辱。也就是說(shuō)振湾,UTF-16的編碼長(zhǎng)度要么是2個(gè)字節(jié)(U+0000到U+FFFF),要么是4個(gè)字節(jié)(U+010000到U+10FFFF)亡脸。
為了能夠區(qū)分它本身是一個(gè)字符押搪,還是需要跟其他兩個(gè)字節(jié)放在一起解讀佛南。在BMP中,從U+D800到U+DFFF之間BMP的區(qū)段是永久保留不映射到字符(從維基百科的圖中D8~DF之間表示unallocated code points
)嵌言。
UTF-16結(jié)論(D800~DFFF)
具體來(lái)說(shuō)嗅回,輔助平面的字符位共有pow(2,20)
個(gè),也就是說(shuō)摧茴,對(duì)應(yīng)這些字符至少需要20個(gè)二進(jìn)制位绵载。UTF-16將這20位拆成兩半,前10位映射在U+D800到U+DBFF(空間大小pow(2,10)
)苛白,稱為高位(H)娃豹,后10位映射在U+DC00到U+DFFF(空間大小pow(2,10)
),稱為低位(L)购裙。這意味著懂版,一個(gè)輔助平面的字符,被拆成兩個(gè)基本平面的字符表示躏率。
HHHH HHHH HHLL LLLL LLLL
????????注意-結(jié)論
高位:D800~DBFF
;
低位:DC00~DFFF
;
所以躯畴,當(dāng)我們遇到兩個(gè)字節(jié),發(fā)現(xiàn)它的碼點(diǎn)在U+D800到U+DBFF之間薇芝,就可以斷定蓬抄,緊跟在后面的兩個(gè)字節(jié)的碼點(diǎn),應(yīng)該在U+DC00到U+DFFF之間夯到,這四個(gè)字節(jié)必須放在一起解讀嚷缭。(而不會(huì)拆成兩個(gè)字節(jié)來(lái)讀)
解釋一下這里為什么是pow(2,20)
,在基本平面之外有16個(gè)輔助平面(即pow(2,4)
)耍贾,而每一個(gè)輔助平面pow(2,16)
個(gè)碼位(輔助平面和基本平面一樣阅爽,每個(gè)碼位里面都包含了256個(gè)碼點(diǎn))。
///輔助平面字符荐开,轉(zhuǎn)碼公式付翁。
/// js代碼
H = Math.floor((c-0x10000) / 0x400)+0xD800
L = (c - 0x10000) % 0x400 + 0xDC00
H為上文提到的高位,L位上文提到的低位誓焦。
舉例說(shuō)明一下:
/// 對(duì)于小于0xFFFF的即基本平面的字符胆敞,為兩個(gè)字節(jié)
U+8D9E = 0x8D9E ///對(duì)應(yīng)的二進(jìn)制格式為:10001101 10011110
/// 對(duì)出于輔助平面的字符
/// 對(duì)于U+1D306
H = Math.floor((0x1D306-0x10000) / 0x400)+0xD800 = d834
L = (0x1D306 - 0x10000) % 0x400 + 0xDC00 = df06
UTF-32
因?yàn)閁TF-32對(duì)每個(gè)字符都使用4字節(jié),就空間而言杂伟,是非常沒有效率的。特別地仍翰,非基本多文種平面的字符在大部分文件中通常很罕見赫粥,以致于它們通常被認(rèn)為不存在占用空間大小的討論,使得UTF-32通常會(huì)是其它編碼的二到四倍予借。
結(jié)論
所以當(dāng)我們?cè)谑褂米址臅r(shí)候越平,通常使用length的時(shí)候频蛔,要看他的編碼方式,并不是一個(gè)字符就代表了一個(gè)字節(jié)有可能是兩個(gè)字節(jié)秦叛、四個(gè)字節(jié)甚至可能最多能到6個(gè)字節(jié)都是有可能的晦溪,這應(yīng)該就能理解Swift中對(duì)于字符串的處理了。
參考文獻(xiàn)
Objc中國(guó)上關(guān)于 NSString 與 Unicode
Unicode代碼圖表
字符編碼筆記:ASCII挣跋,Unicode和UTF-8
什么是UTF-8
Unicode與JavaScript詳解
Unicode編碼及其實(shí)現(xiàn):UTF-16三圆、UTF-8,and more
UTF-16
UTF-32