注:因爲兩邊同步的麻煩,更多更改及調整可參考個人網站:xiaogd.net 上的字符集編碼與亂碼系列,已將字符集編碼系列與亂碼探源系列合併,更新及勘誤等再也不更新到這邊。html
前面談到很多的Unicode,但一直沒有系統地談及Unicode的方方面面,因此本篇文章專門談談Unicode,固然了,Unicode是一個龐大的主題,這裏也是揀些重要的方面談談而已,免不了掛一漏萬。正則表達式
按Unicode官方的說法,Unicode是Unicode Standard(Unicode標準)的簡寫,因此Unicode便是指Unicode標準。網站
按wiki的說法,它是一個計算機工業標準(a computing industry standard)。編碼
下圖來自http://www.unicode.org/standard/WhatIsUnicode.html中的截圖,在這裏我把中文和英文的合在一塊兒spa
這樣一個所謂的一個惟一的數字在Unicode中就叫作碼點。.net
字符集一般又叫」編碼字符集」(coded charset),這裏的coded與」字符集編碼」(charset encoding)中的encoding是不一樣的。翻譯
一個是code,一個是encode,翻譯時均可以譯成」編碼」,但把coded charset譯成」編號字符集」也許更不易引起誤解。設計
碼點(Code Point)便是這裏的code,表示的是一種抽象的數字編號。UTF-X則是最終的encoding。代理
這點如不明白,仍請參見字符集與編碼(二)--編號 vs 編碼。code
U+[XX]XXXX是碼點的表示形式,X表明一個十六制數字,能夠有4-6位,不足4位前補0補足4位,超過則按是幾位就是幾位。如下是碼點的一些具體示例:U+0048,U+4F60,U+1D11E。最後一個是5位的碼點。
有人可能覺得碼點只有4位,並經常將它與UTF-16的編碼搞混,這些都是對碼點的誤解。
它的範圍目前是U+0000~U+10FFFF,理論大小爲10FFFF+1=110000。後一個1表明是65536,由於是16進制,因此前一個1是後一個1的16倍,因此總共有1×16+1=17個的65536的大小,粗略估算爲17×6萬=102萬,因此這是一個百萬級別的數。
準確的值是1114112,通常記爲111萬左右便可。
110000寫成二進制是100010000000000000000,是一個21位的二進制數,咱們知道2^10=K,2^20=K×K=M,即百萬級別,因此2^21理論上限是兩百萬左右。100010000000000000000大小基本上由第一個1決定,因此也就一百萬左右,從這裏也可印證前面的估算。
按照Unicode官方的說法,碼點範圍就這些了,之後也不會再擴充了。
爲了更好分類管理如此龐大的碼點數,把每65536個碼點做爲一個平面,總共17個平面。
由前面可知,碼點的所有範圍能夠均分紅17個65536大小的部分,這裏面的每個部分就是一個平面(Plane)。編號從0開始,第一個平面稱爲Plane 0.
下圖來自http://rishida.net/docs/unicode-tutorial/part2
第一個平面便是BMP(Basic Multilingual Plane 基本多語言平面),也叫Plane 0,它的碼點範圍是U+0000~U+FFFF。這也是咱們最經常使用的平面,平常用到的字符絕大多數都落在這個平面內。
上圖中第一個花花綠綠的平面就是BMP。
UTF-16只須要用兩字節編碼此平面內的字符。
不少人錯誤地把UTF-16當成定長兩字節看待,但只要處理的字符都在這一平面內,通常也不會遇到什麼問題。
後續的16個平面稱爲SP(Supplementary Planes)。顯然,這些碼點已是超過U+FFFF的了,因此已經超過了16位空間的理論上限,對於這些平面內的字符,UTF-16採用了四字節編碼。
注:其中不少平面仍是空的,尚未分配任何字符,只是先規劃了這麼多。
另:有些還屬於私有的,如上圖中的最後兩個Private Use Planes,在此可自定義字符。
Unicode的字符如此之多,即便是最經常使用的BMP,它的碼點空間也有6萬多,若是把這些字符都放到一張圖片上,會是什麼狀況呢?GNU Unifont就製做了一張這樣的圖片。見http://unifoundry.com/pub/unifont-7.0.03/unifont-7.0.03.bmp
提示:打開它須要一點時間,它的像素是4000×4000這個級別!
下圖是它的一個縮略版本。
這是一個256×256=65536的表格,橫向縱向都是從00~FF。
你可能已經注意到上圖中間一大片的區域,沒錯,它就是咱們的漢字,在Unicode中,稱爲CJK統一漢字(CJK:Chinese, Japanese, and Korean,中日韓)。咱們能夠局部放大看一下。
你可能在很多地方見過這種寫法,嚴格來講這只是Unicode最主要的一段中文區域。
你只要稍加計算就可知這一段大小不過是兩萬多一點,\u4E00-\u9FA5(19968-40869),中文怎麼可能只有這兩萬多字呢?
這裏的「天字第一號」字4E00是哪一個字呢?請看上面的圖,它就是「一「字,咱們還能夠看到它上面還有很多的漢字,這就是後來增補的漢字了。因此嚴格來講,這個上限是不許確的。那麼它的下限又是否準確呢?下面是Word的一個插入符號功能的一個截圖
能夠看到9FA5後面也還有很多的漢字,它們中間又還夾雜着一些符號,因此想正確地表示Unicode中的漢字仍是個不小的挑戰。
應該說,Unicode處在不斷髮展中,它有一百多萬的空間,目前也只是定義了十萬左右的字符,還會不斷增長,漢字天然也有可能增長,因此漢字的範圍其實是動態的,變化的。固然了,經常使用的基本落在了這一範圍內,而事實上已經包含了許多的不經常使用漢字,畢竟連只有6千多字的GB2312中都含有大量的不經常使用漢字。在要求不那麼嚴格的應用中,按以上範圍去判斷基本也OK,而「漢字」這一律念實際上也沒有準肯定義,比方說上圖中一些「偏旁部首」,這些是「漢字」嗎?
你可能還注意到前面的BMP縮略圖中有一片空白,這白花花一片亮瞎了咱們的猿眼的是啥呢?正如標題已經告訴你的,這就是所謂的代理區(Surrogate Area)了。
能夠看到這段空白從D8~DF。其中前面的紅色部分D800–DBFF屬於高代理區(High Surrogate Area),後面的藍色部分DC00–DFFF屬於低代理區(Low Surrogate Area),各自的大小均爲4×256=1024。
關於代理區的相關用途,咱們在講到UTF-16編碼時再說。
還能夠看到在它以前是韓文的區域,以後E0開始到F8的則是屬於私有的(private),能夠在這裏定義本身專用的字符。
至此咱們對Unicode的碼點,平面都有了必定的瞭解,但咱們尚未觸及一個重要的方面,那就是碼點到最終編碼的轉換,在Unicode中,這稱爲UTF。
UTF便是Unicode轉換格式(Unicode (or UCS) Transformation Format)
關於UCS:Universal Character Set(統一字符集),也稱ISO/IEC 10646標準,不那麼嚴格的狀況下,能夠認爲它和」Unicode字符集「這一律念是等價的。若有興趣的能夠自行搜索瞭解。
碼點如何轉換成UTF的幾種形式呢?我想這是你們很關心的問題,再發一次前面的一個圖
讓咱們先從最簡單的UTF-32提及
咱們說碼點最大的10FFFF也就21位,而UTF-32採用的定長四字節則是32位,因此它表示全部的碼點不但毫無壓力,反而綽綽有餘,因此只要把碼點的表示形式之前補0的形式補夠32位便可。這種表示的最大缺點是佔用空間太大。
再來看稍複雜一點的UTF-8。
UTF-8是變長的編碼方案,能夠有1,2,3,4四種字節組合。在前面的定長與變長篇章咱們提到UTF-8採用了高位保留方式來區別不一樣變長,以下:
如上,彩色的表示是保留的固定位,X表示是有效編碼位。
單字節最高位都是0,多字節的最高位都是1.
多字節方面,更具體的講,N字節模式,首字節以「N個1再加0」打頭,後跟「N-1」個以「10」打頭的字節。
碼點與字節如何對應?
哪些碼點用哪一種變長呢?能夠先把碼點變成二進制,看它有多少有效位(去掉前導0)就能夠肯定了。
1. 一字節有效編碼位有7位,2^7=128,碼點U+0000~U+007F(0~127)使用一字節。
一字節留給了ASCII,因此UTF-8兼容ASCII。
2. 二字節有效編碼位只有5+6=11位,最多隻有2^11=2048個編碼空間,因此數量衆多的漢字是沒法容身於此的了。碼點U+0080~U+07FF(128~2047)使用二字節。
注意:這裏碼點從128~2047,由於去掉了一字節的碼點,因此不會佔滿2048個編碼空間,是有冗餘的,但你不能把適用於一字節的碼點放到這裏來編碼。下同。
3. 三字節模式可看到光是保留位就達到4+2+2=8位,至關一字節,因此只剩下兩字節16位有效編碼位,它的容量實際也只有65536。碼點U+0800~U+FFFF(2048~65535)使用三字節編碼。
咱們前面說到,一些漢字字典收錄的漢字達到了驚人的10萬級別。基本上,經常使用的漢字都落在了這三字節的空間裏,這就是咱們常說的漢字在UTF-8裏用三字節表示。固然了,這麼說並不嚴謹,若是這10萬的漢字都被收錄進來的話,那些偏門的漢字天然只能被擠到四字節空間上去了。
4. 四字節的能夠看到它的有效位是3+6+6+6=21位,前面說到最大的碼點10FFFF也是21位,U+FFFF以上的增補平面的字符都在這裏來表示。
按照UTF-8的模式,它還能夠擴展到5字節,乃至6字節變長,但Unicode說了碼點就到10FFFF,不擴充了,因此UTF-8最多到四字節就足夠了。
碼點到UTF-8如何轉換?
那麼具體是如何轉換呢,其實不難,來看一個漢字」你「(U+4F60)的轉換示意,以下圖所示:
上圖顯示了一有效位爲15位的碼點到三字節轉換的一個基本原理,咱們還可看到原來4F60中的一頭一尾的兩個4和0在轉換後還存在於最終的三字節結果中。UTF-8三字節模式固定了1110的開頭模式,因此多數漢字老是以1110開頭,換成16進制形式,1110就是字母E。
若是看到一串的16進制有以下的形式:EX XX XX EX XX XX…每三個三個字節前面都是E打頭,那麼它極可能就是一串漢字的UTF-8編碼了。
其它變長字節轉換道理也相似,其中分組從低位開始,高位如不足則補零。這裏就再也不示例了。
最後來看最複雜的UTF-16,在此以前咱們先要理解代理區與代理對等概念。
UTF-16是一種變長的2或4字節編碼模式。對於BMP內的字符使用2字節編碼,其它的則使用4字節組成所謂的代理對來編碼。
什麼是代理區?
在前面的鳥瞰圖中,咱們看到了一片空白的區域,這就是所謂的代理區(Surrogate Area)了,代理區是UTF-16爲了編碼增補平面中的字符而保留的,總共有2048個位置,均分爲高代理區(D800–DBFF)和低代理區(DC00–DFFF)兩部分,各1024,這兩個區組成一個二維的表格,共有1024×1024=2^10×2^10=2^4×2^16=16×65536,因此它剛好能夠表示增補的16個平面中的全部字符。
固然了,說剛好是不對的,顯然代理區就是衝着表示增補平面來設計的,或者至少它們是一塊兒考慮的。
下面的圖片來自wiki
什麼是代理對?
一個高代理區(即上圖中的Lead(頭),行)的加一個低代理區(即上圖中的Trail(尾),列)的編碼組成一對便是一個代理對(Surrogate Pair),必須是這種先高後低的順序,若是出現兩個高,兩個低,或者先低後高,都是非法的。
在圖中能夠看到一些轉換的例子,如
(D8 00 DC 00)—>U+10000,左上角,第一個增補字符
(DB FF DF FF)—>U+10FFFF,右下角,最後一個增補字符
碼點到UTF-16如何轉換?
分紅兩部分:
1. BMP中直接對應,無須作任何轉換;
2. 增補平面SP中,則須要作相應的計算。其實由上圖中的表也可看出,碼點就是從上到下,從左到右排列過去的,因此只需作個簡單的除法,拿到除數和餘數便可肯定行與列。
拿到一個碼點,先減去10000(16進制),再除以400(16進制)(=1024(10進制))就是所在行了,餘數就是所在列了,再加上行與列所在的起始值,就獲得了代理對了。
Lead = (碼點 - 10000) ÷ 400 + D800
Trail = (碼點 - 10000) % 400 + DC00
下面之前面的U+1D11E具體示例了代理對的計算:
Lead = (1D11E - 10000) ÷ 400 + DB00 = D11E ÷ 400 + D800 = 34 + D800 = D834
Trail = (1D11E - 10000) % 400 + DC00 = D11E % 400 + DC00 = 11E + DC00 = DD1E
因此,碼點U+1D11E對應的代理對便是 D834 DD1E。
注意:以上計算方式僅用於說明轉換原理,不表明實際採用的計算方式。一個碼點減去1000016後實際最多隻有20位,再除以400(=2^10=10000000000(2進制)),這個除數實際是一個二進制整數,至關於十進制中整十整百的數。因此結果實際上低10位上的就是餘數,而高10位(或者不到10位)上就是商,能夠經過更爲快速的移位操做實現。舉個十進制的例子,就比如是「1234÷100=12▪▪▪▪▪▪34」,你都不須要拿筆去算。應該說,代理區的設計是有效率上的考慮的,若是咱們要作轉換,應該考慮是否有系統API可供調用,而不要自行去實現。
關於Unicode的基本知識,就講到這裏。