字符集与编码(四)——Unicode
2019獨角獸企業(yè)重金招聘Python工程師標準>>>
注:由于兩邊同步的麻煩,更多更改及調(diào)整可參考我的網(wǎng)站:xiaogd.net 上的字符集編碼與亂碼系列,已將字符集編碼系列與亂碼探源系列合并,更新及勘誤等不再更新到這邊。
前面談到不少的Unicode,但一直沒有系統(tǒng)地談及Unicode的方方面面,所以本篇文章專門談?wù)刄nicode,當(dāng)然了,Unicode是一個龐大的主題,這里也是揀些重要的方面談?wù)劧?#xff0c;免不了掛一漏萬。
什么是Unicode?
按Unicode官方的說法,Unicode是Unicode Standard(Unicode標準)的簡寫,所以Unicode即是指Unicode標準。
按wiki的說法,它是一個計算機工業(yè)標準(a computing industry standard)。
下圖來自http://www.unicode.org/standard/WhatIsUnicode.html中的截圖,在這里我把中文和英文的合在一起
這樣一個所謂的一個唯一的數(shù)字在Unicode中就叫做碼點。
Unicode中的碼點是什么?
字符集通常又叫”編碼字符集”(coded charset),這里的coded與”字符集編碼”(charset encoding)中的encoding是不同的。
一個是code,一個是encode,翻譯時都可以譯成”編碼”,但把coded charset譯成”編號字符集”也許更不易引發(fā)誤解。
碼點(Code Point)即是這里的code,表示的是一種抽象的數(shù)字編號。UTF-X則是最終的encoding。
這點如不明白,仍請參見字符集與編碼(二)--編號 vs 編碼。
碼點的表示形式與范圍是?
U+[XX]XXXX是碼點的表示形式,X代表一個十六制數(shù)字,可以有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萬,所以這是一個百萬級別的數(shù)。
準確的值是1114112,一般記為111萬左右即可。
110000寫成二進制是100010000000000000000,是一個21位的二進制數(shù),我們知道2^10=K,2^20=K×K=M,即百萬級別,所以2^21理論上限是兩百萬左右。100010000000000000000大小基本上由第一個1決定,所以也就一百萬左右,從這里也可印證前面的估算。
按照Unicode官方的說法,碼點范圍就這些了,以后也不會再擴充了。
為了更好分類管理如此龐大的碼點數(shù),把每65536個碼點作為一個平面,總共17個平面。
平面,BMP,SP
什么是平面?
由前面可知,碼點的全部范圍可以均分成17個65536大小的部分,這里面的每一個部分就是一個平面(Plane)。編號從0開始,第一個平面稱為Plane 0.
下圖來自http://rishida.net/docs/unicode-tutorial/part2
什么是BMP?
第一個平面即是BMP(Basic Multilingual Plane 基本多語言平面),也叫Plane 0,它的碼點范圍是U+0000~U+FFFF。這也是我們最常用的平面,日常用到的字符絕大多數(shù)都落在這個平面內(nèi)。
上圖中第一個花花綠綠的平面就是BMP。
UTF-16只需要用兩字節(jié)編碼此平面內(nèi)的字符。
很多人錯誤地把UTF-16當(dāng)成定長兩字節(jié)看待,但只要處理的字符都在這一平面內(nèi),一般也不會遇到什么問題。
什么是增補平面?
后續(xù)的16個平面稱為SP(Supplementary Planes)。顯然,這些碼點已經(jīng)是超過U+FFFF的了,所以已經(jīng)超過了16位空間的理論上限,對于這些平面內(nèi)的字符,UTF-16采用了四字節(jié)編碼。
注:其中很多平面還是空的,還沒有分配任何字符,只是先規(guī)劃了這么多。
另:有些還屬于私有的,如上圖中的最后兩個Private Use Planes,在此可自定義字符。
鳥瞰BMP字符集
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。
CJK統(tǒng)一漢字
你可能已經(jīng)注意到上圖中間一大片的區(qū)域,沒錯,它就是我們的漢字,在Unicode中,稱為CJK統(tǒng)一漢字(CJK:Chinese, Japanese, and Korean,中日韓)。我們可以局部放大看一下。
正則表達式[\u4E00-\u9FA5]來匹配中文的問題在哪?
你可能在不少地方見過這種寫法,嚴格來說這只是Unicode最主要的一段中文區(qū)域。
你只要稍加計算就可知這一段大小不過是兩萬多一點,\u4E00-\u9FA5(19968-40869),中文怎么可能只有這兩萬多字呢?
這里的“天字第一號”字4E00是哪個字呢?請看上面的圖,它就是“一“字,我們還可以看到它上面還有不少的漢字,這就是后來增補的漢字了。所以嚴格來說,這個上限是不準確的。那么它的下限又是否準確呢?下面是Word的一個插入符號功能的一個截圖
可以看到9FA5后面也還有不少的漢字,它們中間又還夾雜著一些符號,所以想正確地表示Unicode中的漢字還是個不小的挑戰(zhàn)。
應(yīng)該說,Unicode處在不斷發(fā)展中,它有一百多萬的空間,目前也只是定義了十萬左右的字符,還會不斷增加,漢字自然也有可能增加,所以漢字的范圍實際上是動態(tài)的,變化的。當(dāng)然了,常用的基本落在了這一范圍內(nèi),而事實上已經(jīng)包含了許多的不常用漢字,畢竟連只有6千多字的GB2312中都含有大量的不常用漢字。在要求不那么嚴格的應(yīng)用中,按以上范圍去判斷基本也OK,而“漢字”這一概念實際上也沒有準確定義,比方說上圖中一些“偏旁部首”,這些是“漢字”嗎?
代理區(qū)
你可能還注意到前面的BMP縮略圖中有一片空白,這白花花一片亮瞎了我們的猿眼的是啥呢?正如標題已經(jīng)告訴你的,這就是所謂的代理區(qū)(Surrogate Area)了。
可以看到這段空白從D8~DF。其中前面的紅色部分D800–DBFF屬于高代理區(qū)(High Surrogate Area),后面的藍色部分DC00–DFFF屬于低代理區(qū)(Low Surrogate Area),各自的大小均為4×256=1024。
關(guān)于代理區(qū)的相關(guān)用途,我們在講到UTF-16編碼時再說。
還可以看到在它之前是韓文的區(qū)域,之后E0開始到F8的則是屬于私有的(private),可以在這里定義自己專用的字符。
至此我們對Unicode的碼點,平面都有了一定的了解,但我們還沒有觸及一個重要的方面,那就是碼點到最終編碼的轉(zhuǎn)換,在Unicode中,這稱為UTF。
什么是UTF?
UTF即是Unicode轉(zhuǎn)換格式(Unicode (or UCS) Transformation Format)
關(guān)于UCS:Universal Character Set(統(tǒng)一字符集),也稱ISO/IEC 10646標準,不那么嚴格的情況下,可以認為它和”Unicode字符集“這一概念是等價的。如有興趣的可以自行搜索了解。
碼點如何轉(zhuǎn)換成UTF的幾種形式呢?我想這是大家很關(guān)心的問題,再發(fā)一次前面的一個圖
讓我們先從最簡單的UTF-32說起
UTF-32
我們說碼點最大的10FFFF也就21位,而UTF-32采用的定長四字節(jié)則是32位,所以它表示所有的碼點不但毫無壓力,反而綽綽有余,所以只要把碼點的表示形式以前補0的形式補夠32位即可。這種表示的最大缺點是占用空間太大。
再來看稍復(fù)雜一點的UTF-8。
UTF-8
UTF-8是變長的編碼方案,可以有1,2,3,4四種字節(jié)組合。在前面的定長與變長篇章我們提到UTF-8采用了高位保留方式來區(qū)別不同變長,如下:
如上,彩色的表示是保留的固定位,X表示是有效編碼位。
單字節(jié)最高位都是0,多字節(jié)的最高位都是1.
多字節(jié)方面,更具體的講,N字節(jié)模式,首字節(jié)以“N個1再加0”打頭,后跟“N-1”個以“10”打頭的字節(jié)。
碼點與字節(jié)如何對應(yīng)?
哪些碼點用哪種變長呢?可以先把碼點變成二進制,看它有多少有效位(去掉前導(dǎo)0)就可以確定了。
1. 一字節(jié)有效編碼位有7位,2^7=128,碼點U+0000~U+007F(0~127)使用一字節(jié)。
一字節(jié)留給了ASCII,所以UTF-8兼容ASCII。
2. 二字節(jié)有效編碼位只有5+6=11位,最多只有2^11=2048個編碼空間,所以數(shù)量眾多的漢字是無法容身于此的了。碼點U+0080~U+07FF(128~2047)使用二字節(jié)。
注意:這里碼點從128~2047,因為去掉了一字節(jié)的碼點,所以不會占滿2048個編碼空間,是有冗余的,但你不能把適用于一字節(jié)的碼點放到這里來編碼。下同。
3. 三字節(jié)模式可看到光是保留位就達到4+2+2=8位,相當(dāng)一字節(jié),所以只剩下兩字節(jié)16位有效編碼位,它的容量實際也只有65536。碼點U+0800~U+FFFF(2048~65535)使用三字節(jié)編碼。
我們前面說到,一些漢字字典收錄的漢字達到了驚人的10萬級別。基本上,常用的漢字都落在了這三字節(jié)的空間里,這就是我們常說的漢字在UTF-8里用三字節(jié)表示。當(dāng)然了,這么說并不嚴謹,如果這10萬的漢字都被收錄進來的話,那些偏門的漢字自然只能被擠到四字節(jié)空間上去了。
4. 四字節(jié)的可以看到它的有效位是3+6+6+6=21位,前面說到最大的碼點10FFFF也是21位,U+FFFF以上的增補平面的字符都在這里來表示。
按照UTF-8的模式,它還可以擴展到5字節(jié),乃至6字節(jié)變長,但Unicode說了碼點就到10FFFF,不擴充了,所以UTF-8最多到四字節(jié)就足夠了。
碼點到UTF-8如何轉(zhuǎn)換?
那么具體是如何轉(zhuǎn)換呢,其實不難,來看一個漢字”你“(U+4F60)的轉(zhuǎn)換示意,如下圖所示:
上圖顯示了一有效位為15位的碼點到三字節(jié)轉(zhuǎn)換的一個基本原理,我們還可看到原來4F60中的一頭一尾的兩個4和0在轉(zhuǎn)換后還存在于最終的三字節(jié)結(jié)果中。UTF-8三字節(jié)模式固定了1110的開頭模式,所以多數(shù)漢字總是以1110開頭,換成16進制形式,1110就是字母E。
如果看到一串的16進制有如下的形式:EX XX XX EX XX XX…每三個三個字節(jié)前面都是E打頭,那么它很可能就是一串漢字的UTF-8編碼了。
其它變長字節(jié)轉(zhuǎn)換道理也類似,其中分組從低位開始,高位如不足則補零。這里就不再示例了。
最后來看最復(fù)雜的UTF-16,在此之前我們先要理解代理區(qū)與代理對等概念。
UTF-16
UTF-16是一種變長的2或4字節(jié)編碼模式。對于BMP內(nèi)的字符使用2字節(jié)編碼,其它的則使用4字節(jié)組成所謂的代理對來編碼。
什么是代理區(qū)?
在前面的鳥瞰圖中,我們看到了一片空白的區(qū)域,這就是所謂的代理區(qū)(Surrogate Area)了,代理區(qū)是UTF-16為了編碼增補平面中的字符而保留的,總共有2048個位置,均分為高代理區(qū)(D800–DBFF)和低代理區(qū)(DC00–DFFF)兩部分,各1024,這兩個區(qū)組成一個二維的表格,共有1024×1024=2^10×2^10=2^4×2^16=16×65536,所以它恰好可以表示增補的16個平面中的所有字符。
當(dāng)然了,說恰好是不對的,顯然代理區(qū)就是沖著表示增補平面來設(shè)計的,或者至少它們是一起考慮的。
下面的圖片來自wiki
什么是代理對?
一個高代理區(qū)(即上圖中的Lead(頭),行)的加一個低代理區(qū)(即上圖中的Trail(尾),列)的編碼組成一對即是一個代理對(Surrogate Pair),必須是這種先高后低的順序,如果出現(xiàn)兩個高,兩個低,或者先低后高,都是非法的。
在圖中可以看到一些轉(zhuǎn)換的例子,如
(D8 00 DC 00)—>U+10000,左上角,第一個增補字符
(DB FF DF FF)—>U+10FFFF,右下角,最后一個增補字符
碼點到UTF-16如何轉(zhuǎn)換?
分成兩部分:
1. BMP中直接對應(yīng),無須做任何轉(zhuǎn)換;
2. 增補平面SP中,則需要做相應(yīng)的計算。其實由上圖中的表也可看出,碼點就是從上到下,從左到右排列過去的,所以只需做個簡單的除法,拿到除數(shù)和余數(shù)即可確定行與列。
拿到一個碼點,先減去10000(16進制),再除以400(16進制)(=1024(10進制))就是所在行了,余數(shù)就是所在列了,再加上行與列所在的起始值,就得到了代理對了。
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對應(yīng)的代理對即是 D834 DD1E。
注意:以上計算方式僅用于說明轉(zhuǎn)換原理,不代表實際采用的計算方式。一個碼點減去1000016后實際最多只有20位,再除以400(=2^10=10000000000(2進制)),這個除數(shù)實際是一個二進制整數(shù),相當(dāng)于十進制中整十整百的數(shù)。所以結(jié)果實際上低10位上的就是余數(shù),而高10位(或者不到10位)上就是商,可以通過更為快速的移位操作實現(xiàn)。舉個十進制的例子,就好比是“1234÷100=12??????34”,你都不需要拿筆去算。應(yīng)該說,代理區(qū)的設(shè)計是有效率上的考慮的,如果我們要做轉(zhuǎn)換,應(yīng)該考慮是否有系統(tǒng)API可供調(diào)用,而不要自行去實現(xiàn)。
關(guān)于Unicode的基本知識,就講到這里。
轉(zhuǎn)載于:https://my.oschina.net/goldenshaw/blog/310331
總結(jié)
以上是生活随笔為你收集整理的字符集与编码(四)——Unicode的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎样学好C语言,一个成功人士的心得!
- 下一篇: NHibernate应用二:第一个NHi