今天妒蔚,在學(xué)習(xí) Node.js 中的 Buffer 對象時,注意到它的 alloc 和 from 方法會默認(rèn)用 UTF-8 編碼屑埋,在數(shù)組中每位對應(yīng) 1 字節(jié)的十六進(jìn)制數(shù)肆汹。想到了之間學(xué)習(xí) ES6 時關(guān)于字符串的 Unicode 表示法,突然就很想知道 UTF-16 是如何進(jìn)行編碼的输虱,我嘗試將一些漢字轉(zhuǎn)換成二進(jìn)制數(shù)些楣,然后簡單的按 2 個字節(jié)一組轉(zhuǎn)換成十六進(jìn)制,發(fā)現(xiàn)對于那些碼點較大的漢字宪睹,結(jié)果并不僅僅是簡單的二進(jìn)制轉(zhuǎn)十六進(jìn)制愁茁。于是,我開始在網(wǎng)上找資料亭病,決心徹底弄明白 Unicode 編碼鹅很。
Unicode編碼則是采用雙字節(jié)16位來進(jìn)行編號,可編65536字符罪帖,基本上包含了世界上所有的語言字符
促煮,它也就成為了全世界一種通用的編碼,而且用十六進(jìn)制4位表示一個編碼整袁,非常簡結(jié)直觀菠齿,為大多數(shù)開發(fā)者所接受,特別是十六進(jìn)制編碼后坐昙,可以解決漢字在js再編碼過程中出現(xiàn)亂碼問題绳匀,提高解釋速度,我們建議在js腳本中使用十六進(jìn)制unicode編碼炸客。
UniCode漢字轉(zhuǎn)換襟士,網(wǎng)上很多,但相對比較好使的比較少嚷量,大都寫法一樣乖仇,轉(zhuǎn)換的效果差別不大,或多或少有些遺憾苹支,我這找到個相對較好的赊时,能直接轉(zhuǎn)換標(biāo)點符號的轉(zhuǎn)換器宣渗。
地址:http://javawind.net/tools/native2ascii.jsp?action=transform
另附上Unicode編號表,這下完美了
ASCII碼
在學(xué)校學(xué) C 語言的時候梨州,了解到一些計算機內(nèi)部的機制痕囱,知道所有的信息最終都表示為一個二進(jìn)制的字符串,每一個二進(jìn)制位有 0 和 1 兩種狀態(tài)暴匠,通過不同的排列組合鞍恢,使用 0 和 1 就可以表示世界上所有的東西,感覺有點中國“太極”的感覺——“太極生兩儀每窖,兩儀生四象帮掉,四象生八卦”。
在計算機種中窒典,1 字節(jié)對應(yīng) 8 位二進(jìn)制數(shù)蟆炊,而每位二進(jìn)制數(shù)有 0、1 兩種狀態(tài)瀑志,因此 1 字節(jié)可以組合出 256 種狀態(tài)涩搓。
如果這 256 中狀態(tài)每一個都對應(yīng)一個符號,就能通過 1 字節(jié)的數(shù)據(jù)表示 256 個字符劈猪。美國人于是就制定了一套編碼(其實就是個字典)昧甘,描述英語中的字符和這 8 位二進(jìn)制數(shù)的對應(yīng)關(guān)系,這被稱為 ASCII 碼战得。
ASCII 碼一共定義了 128 個字符疾层,例如大寫的字母 A 是 65(這是十進(jìn)制數(shù),對應(yīng)二進(jìn)制是0100 0001)贡避。這 128 個字符只使用了 8 位二進(jìn)制數(shù)中的后面 7 位痛黎,最前面的一位統(tǒng)一規(guī)定為 0。
歷史問題
英語用 128 個字符來編碼完全是足夠的刮吧,但是用來表示其他語言湖饱,128 個字符是遠(yuǎn)遠(yuǎn)不夠的。于是杀捻,一些歐洲的國家就決定井厌,將 ASCII 碼中閑置的最高位利用起來,這樣一來就能表示 256 個字符致讥。但是仅仆,這里又有了一個問題,那就是不同的國家的字符集可能不同垢袱,就算它們都能用 256 個字符表示全墓拜,但是同一個碼
點(也就是 8 位二進(jìn)制數(shù))表示的字符可能可能不同。例如请契,144 在阿拉伯人的 ASCII 碼中是 ?咳榜,而在俄羅斯的 ASCII 碼中是 ?夏醉。
因此,ASCII 碼的問題在于盡管所有人都在 0 - 127 號字符上達(dá)成了一致涌韩,但對于 128 - 255 號字符上卻有很多種不同的解釋畔柔。與此同時,亞洲語言有更多的字符需要被存儲臣樱,一個字節(jié)已經(jīng)不夠用了靶擦。于是,人們開始使用兩個字節(jié)來存儲字符雇毫。
各種各樣的編碼方式成了系統(tǒng)開發(fā)者的噩夢玄捕,因為他們想把軟件賣到國外。于是嘴拢,他們提出了一個“內(nèi)碼表”的概念桩盲,可以切換到相應(yīng)語言的一個內(nèi)碼表寂纪,這樣才能顯示相應(yīng)語言的字母席吴。在這種情況下,如果使用多語種捞蛋,那么就需要頻繁的在內(nèi)碼表內(nèi)進(jìn)行切換孝冒。
Unicode
最終,美國人意識到他們應(yīng)該提出一種標(biāo)準(zhǔn)方案來展示世界上所有語言中的所有字符拟杉,出于這個目的庄涡,Unicode誕生了。
Unicode 當(dāng)然是一本很厚的字典搬设,記錄著世界上所有字符對應(yīng)的一個數(shù)字穴店。具體是怎樣的對應(yīng)關(guān)系,又或者說是如何進(jìn)行劃分的拿穴,就不是我們考慮的問題了泣洞,我們只用知道Unicode 給所有的字符指定了一個數(shù)字用來表示該字符。
對于 Unicode 有一些誤解默色,它僅僅只是一個字符集球凰,規(guī)定了符合對應(yīng)的二進(jìn)制代碼,至于這個二進(jìn)制代碼如何存儲則沒有任何規(guī)定腿宰。它的想法很簡單呕诉,就是為每個字符規(guī)定一個 用來表示該字符的數(shù)字,僅此而已吃度。
Unicode 編碼方案
之前提到甩挫,Unicode 沒有規(guī)定字符對應(yīng)的二進(jìn)制碼如何存儲。以漢字“漢”為例椿每,它的 Unicode 碼點是 0x6c49捶闸,對應(yīng)的二進(jìn)制數(shù)是 110110001001001夜畴,二進(jìn)制數(shù)有 15 位,這也就說明了它至少需要 2 個字節(jié)來表示删壮√盎妫可以想象,在 Unicode 字典中往后的字符可能就需要 3 個字節(jié)或者 4 個字節(jié)央碟,甚至更多字節(jié)來表示了税灌。
這就導(dǎo)致了一些問題,計算機怎么知道你這個 2 個字節(jié)表示的是一個字符亿虽,而不是分別表示兩個字符呢菱涤?這里我們可能會想到,那就取個最大的洛勉,假如 Unicode 中最大的字符用 4 字節(jié)就可以表示了粘秆,那么我們就將所有的字符都用 4 個字節(jié)來表示,不夠的就往前面補 0收毫。這樣確實可以解決編碼問題攻走,但是卻造成了空間的極大浪費,如果是一個英文文檔此再,那文件大小就大出了 3 倍昔搂,這顯然是無法接受的。
于是输拇,為了較好的解決 Unicode 的編碼問題摘符, UTF-8 和 UTF-16 兩種當(dāng)前比較流行的編碼方式誕生了。當(dāng)然還有一個 UTF-32 的編碼方式策吠,也就是上述那種定長編碼逛裤,字符統(tǒng)一使用 4 個字節(jié),雖然看似方便猴抹,但是卻不如另外兩種編碼方式使用廣泛带族。
UTF-8
UTF-8 是一個非常驚艷的編碼方式,漂亮的實現(xiàn)了對 ASCII 碼的向后兼容洽糟,以保證 Unicode 可以被大眾接受炉菲。
UTF-8 是目前互聯(lián)網(wǎng)上使用最廣泛的一種 Unicode 編碼方式,它的最大特點就是可變長坤溃。它可以使用 1 - 4 個字節(jié)表示一個字符拍霜,根據(jù)字符的不同變換長度。
編碼規(guī)則如下:
對于單個字節(jié)的字符薪介,第一位設(shè)為 0祠饺,后面的 7 位對應(yīng)這個字符的 Unicode 碼點。因此汁政,對于英文中的 0 - 127 號字符道偷,與 ASCII 碼完全相同缀旁。這意味著 ASCII 碼那個年代的文檔用 UTF-8 編碼打開完全沒有問題。
對于需要使用 N 個字節(jié)來表示的字符(N > 1)勺鸦,第一個字節(jié)的前 N 位都設(shè)為 1并巍,第 N + 1 位設(shè)為0,剩余的 N - 1 個字節(jié)的前兩位都設(shè)位 10换途,剩下的二進(jìn)制位則使用這個字符的 Unicode 碼點來填充懊渡。
編碼規(guī)則如下:
UNICODE 十六進(jìn)制碼點范圍 UTF-8 二進(jìn)制
0000 0000 - 0000 007F 0xxxxxxx
0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
根據(jù)上面編碼規(guī)則對照表,進(jìn)行 UTF-8 編碼和解碼就簡單多了军拟。下面以漢字“漢”為利剃执,具體說明如何進(jìn)行 UTF-8 編碼和解碼。
“漢”的 Unicode 碼點是 0x6c49(110 1100 0100 1001)懈息,通過上面的對照表可以發(fā)現(xiàn)肾档,0x0000 6c49 位于第三行的范圍,那么得出其格式為 1110xxxx 10xxxxxx 10xxxxxx辫继。接著怒见,從“漢”的二進(jìn)制數(shù)最后一位開始,從后向前依次填充對應(yīng)格式中的 x骇两,多出的 x 用 0 補上速种。這樣姜盈,就得到了“漢”的 UTF-8 編碼為 11100110 10110001 10001001低千,轉(zhuǎn)換成十六進(jìn)制就是 0xE6 0xB7 0x89。
解碼的過程也十分簡單:如果一個字節(jié)的第一位是 0 馏颂,則說明這個字節(jié)對應(yīng)一個字符示血;如果一個字節(jié)的第一位1,那么連續(xù)有多少個 1救拉,就表示該字符占用多少個字節(jié)难审。
UTF-16
在了解 UTF-16 編碼方式之前,先了解一下另外一個概念——"平面"亿絮。
在上面的介紹中告喊,提到了 Unicode 是一本很厚的字典,她將全世界所有的字符定義在一個集合里派昧。這么多的字符不是一次性定義的黔姜,而是分區(qū)定義。每個區(qū)可以存放 65536 個($2^{16}$)字符蒂萎,稱為一個平面(plane)秆吵。目前,一共有 17 個($2^{5}$)平面五慈,也就是說纳寂,整個 Unicode 字符集的大小現(xiàn)在是 $2^{21}$主穗。
最前面的 65536 個字符位,稱為基本平面(簡稱 BMP )毙芜,它的碼點范圍是從 0 到 $2^{16}-1$忽媒,寫成 16 進(jìn)制就是從 U+0000 到 U+FFFF。所有最常見的字符都放在這個平面腋粥,這是 Unicode 最先定義和公布的一個平面猾浦。剩下的字符都放在輔助平面(簡稱 SMP ),碼點范圍從 U+010000 到 U+10FFFF灯抛。
基本了解了平面的概念后金赦,再說回到 UTF-16。UTF-16 編碼介于 UTF-32 與 UTF-8 之間对嚼,同時結(jié)合了定長和變長兩種編碼方法的特點夹抗。它的編碼規(guī)則很簡單:基本平面的字符占用 2 個字節(jié),輔助平面的字符占用 4 個字節(jié)纵竖。也就是說漠烧,UTF-16 的編碼長度要么是 2 個字節(jié)(U+0000 到 U+FFFF),要么是 4 個字節(jié)(U+010000 到 U+10FFFF)靡砌。那么問題來了已脓,當(dāng)我們遇到兩個字節(jié)時,到底是把這兩個字節(jié)當(dāng)作一個字符還是與后面的兩個字節(jié)一起當(dāng)作一個字符呢通殃?
這里有一個很巧妙的地方度液,在基本平面內(nèi),從 U+D800 到 U+DFFF 是一個空段画舌,即這些碼點不對應(yīng)任何字符堕担。因此,這個空段可以用來映射輔助平面的字符曲聂。
輔助平面的字符位共有 $2^{20}$ 個霹购,因此表示這些字符至少需要 20 個二進(jìn)制位。UTF-16 將這 20 個二進(jìn)制位分成兩半朋腋,前 10 位映射在 U+D800 到 U+DBFF齐疙,稱為高位(H),后 10 位映射在 U+DC00 到 U+DFFF旭咽,稱為低位(L)贞奋。這意味著,一個輔助平面的字符轻专,被拆成兩個基本平面的字符表示忆矛。
因此,當(dāng)我們遇到兩個字節(jié),發(fā)現(xiàn)它的碼點在 U+D800 到 U+DBFF 之間催训,就可以斷定洽议,緊跟在后面的兩個字節(jié)的碼點,應(yīng)該在 U+DC00 到 U+DFFF 之間漫拭,這四個字節(jié)必須放在一起解讀亚兄。
接下來,以漢字"??"為例采驻,說明 UTF-16 編碼方式是如何工作的审胚。
漢字"??"的 Unicode 碼點為 0x20BB7,該碼點顯然超出了基本平面的范圍(0x0000 - 0xFFFF)礼旅,因此需要使用四個字節(jié)表示膳叨。首先用 0x20BB7 - 0x10000 計算出超出的部分,然后將其用 20 個二進(jìn)制位表示(不足前面補 0 )痘系,結(jié)果為0001000010 1110110111菲嘴。接著,將前 10 位映射到 U+D800 到 U+DBFF 之間汰翠,后 10 位映射到 U+DC00 到 U+DFFF 即可龄坪。U+D800 對應(yīng)的二進(jìn)制數(shù)為 1101100000000000,直接填充后面的 10 個二進(jìn)制位即可复唤,得到 1101100001000010健田,轉(zhuǎn)成 16 進(jìn)制數(shù)則為 0xD842。同理可得佛纫,低位為 0xDFB7妓局。因此得出漢字"??"的 UTF-16 編碼為 0xD842 0xDFB7。
Unicode3.0 中給出了輔助平面字符的轉(zhuǎn)換公式:
H = Math.floor((c-0x10000) / 0x400)+0xD800
L = (c - 0x10000) % 0x400 + 0xDC00
根據(jù)編碼公式雳旅,可以很方便的計算出字符的 UTF-16 編碼跟磨。