
寫在前面
如果你是iOS開發(fā)者,并且在處理NSString字符上遇到了一些問題,強(qiáng)烈建議去看看Objc中國上關(guān)于 NSString 與 Unicode。
簡介
Unicode對世界上大部分的文字系統(tǒng)進(jìn)行了整理、編碼,使得電腦可以用更為簡單的方式來呈現(xiàn)和處理文字。這是維基百科對Unicode下的定義。
Unicode的實(shí)現(xiàn)方式包含了UTF-8、UTF-16(字符用兩個(gè)字節(jié)或者四個(gè)字節(jié)表示)和UTF-32(用四個(gè)字節(jié)來表示),下面對面一一進(jìn)行介紹。
UTF-8
UTF-8的最明顯的一個(gè)特點(diǎn)是它是變長的,它可以使用1到4個(gè)字節(jié)表示一個(gè)符號,根據(jù)不同的符號變化字節(jié)長度。
先把阮一峰在《字符編碼筆記:ASCII,Unicode和UTF-8》中對UTF-8的編寫規(guī)則的一個(gè)總結(jié)放出來。
??????UTF-8的編碼規(guī)則很簡單,只有二條:
1、對于單字節(jié)的符號,字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號的unicode碼。因此對于英語字母,UTF-8編碼和ASCII碼是相同的。
2、對于n字節(jié)的符號(n>1),第一個(gè)字節(jié)的前n位都設(shè)為1,第n+1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進(jìn)制位,全部為這個(gè)符號的unicode碼。
提出問題:第一個(gè)字節(jié)前面n位設(shè)1是為了知道當(dāng)前字符占用多少字節(jié),而后面字節(jié)的前兩位字節(jié)為什么要設(shè)置為10呢?下面會馬上進(jìn)行解釋。
現(xiàn)在我們來具體的分析一下Unicode的不同范圍:下面中前兩個(gè)描述是否為ASCII,后兩個(gè)描述多字節(jié)序列
-
U+0000到U+007F(ASCII)
從U+0000到U+007F被編碼為0x00~0x7F的單字節(jié),這是ASCII碼的所有字符,一共128個(gè)字符,所以Unicode是完全用來容納ASCII的。回答上面提出的問題:后面字節(jié)的前兩位一律設(shè)為10(
10000000也就是80)是因?yàn)楸仨氁笥?F才和ASCII碼分開。 大于 U+007F(非 ASCII)
所有大于 U+007F 的字符被編碼為一串多字節(jié)序列,這樣就可以區(qū)分一串多字節(jié)序列是多字節(jié)碼還是 ASCII 碼。0xFE 和 0xFF 不會被用于 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ù)的原因),如果在沒有限制的情況下,通過上面的結(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 編碼字符最長可達(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
/// 同樣我們來驗(yàn)證一下阮一峰舉例的“嚴(yán)”字
/// 4E25 同樣屬于上訴的第三類 ,對應(yīng)的二進(jìn)制 => 0100 1110 0010 0101
/// 11100100 10111000 10100101 得到的結(jié)果和他文章中的一樣。
到這里我自己覺得應(yīng)該是把UTF-8的編碼方式說清楚了,最后再來一個(gè)編碼的順序(很適合于我的方式)
編碼的順序
對于單字節(jié):
直接將其轉(zhuǎn)換為八位的二進(jìn)制就可以了;
對于多字節(jié):
- 1.找到Unicode碼對應(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位長的整數(shù)(即碼元)的序列,用于數(shù)據(jù)存儲或傳遞。
這里需要說明一下基本多文種平面-BMP和輔助平面-SMP,在維基百科中每一個(gè)平面相關(guān)的圖片下面都說了"每個(gè)寫著數(shù)字的格子代表256個(gè)碼點(diǎn)",即00~FF。例如:位于BMP中00格子中的一個(gè)碼點(diǎn)表示為:0x00E5。
下面同樣用一下阮一峰的規(guī)則總結(jié):
基本平面的字符占用2個(gè)字節(jié),輔助平面的字符占用4個(gè)字節(jié)。也就是說,UTF-16的編碼長度要么是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)
具體來說,輔助平面的字符位共有pow(2,20)個(gè),也就是說,對應(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é)必須放在一起解讀。(而不會拆成兩個(gè)字節(jié)來讀)
解釋一下這里為什么是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位上文提到的低位。
舉例說明一下:
/// 對于小于0xFFFF的即基本平面的字符,為兩個(gè)字節(jié)
U+8D9E = 0x8D9E ///對應(yīng)的二進(jìn)制格式為:10001101 10011110
/// 對出于輔助平面的字符
/// 對于U+1D306
H = Math.floor((0x1D306-0x10000) / 0x400)+0xD800 = d834
L = (0x1D306 - 0x10000) % 0x400 + 0xDC00 = df06
UTF-32
因?yàn)閁TF-32對每個(gè)字符都使用4字節(jié),就空間而言,是非常沒有效率的。特別地,非基本多文種平面的字符在大部分文件中通常很罕見,以致于它們通常被認(rèn)為不存在占用空間大小的討論,使得UTF-32通常會是其它編碼的二到四倍。
結(jié)論
所以當(dāng)我們在使用字符串的時(shí)候,通常使用length的時(shí)候,要看他的編碼方式,并不是一個(gè)字符就代表了一個(gè)字節(jié)有可能是兩個(gè)字節(jié)、四個(gè)字節(jié)甚至可能最多能到6個(gè)字節(jié)都是有可能的,這應(yīng)該就能理解Swift中對于字符串的處理了。
參考文獻(xiàn)
Objc中國上關(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