字符集和編碼——Unicode(UTF&UCS)深度歷險

Mr.Johness發表於2013-09-15

  計算機網路誕生後,大家慢慢地發現一個問題:一個位元組放不下一個字元了!因為需要交流,本地化的文字需要能夠被支援。

  最初的字符集使用7bit來儲存字元,因為那時只需要存下一些英文字母和符號。後來雖然擴充套件到使用8bit來儲存一個字元了(這種方式被國際標準化組織收錄,成為ISO8859-1。在字符集發展歷程中國際標準化組織一直髮揮著重要作用。),也還是無法儲存諸如中文的字元。

  混亂的年代到來了。為了儲存下自己的文字,各個國家和地區(多為非拉丁語系的民族,因為這些語種字元數很龐大)各自使用兩個位元組即16bit來存放一個字元。他們把首位元組的前2^7個位留給一個位元組能存下的字元(如英文字母和標點符號),而後的位和後面的位元組一起組成適用於本地文字的字元。這中方式一直沿用至今,如GB2312、GBK(此編碼為微軟為簡體中文使用者設計的)、GB18030、BIG5等。使用這種方式有一個問題:不同的數值(假如我們把字元換算成數字)在不同的字符集可能有不同的意義,甚至使用不同字型也會呈現出不同的效果!而且從一個字符集到另一個字符集的轉化也會非常麻煩!

  標準化一直在進行。為了解決上述麻煩,各種機構都做出了不同的努力。以微軟為代表的作業系統可能更多的是提供使用者可選擇的語言和區域設定,並使用如CodePage(內碼表,Windows作業系統對不同地區不同字符集的支援方式。如GBK為CP936)來隔離差異,國際標準化組織(ISO)編纂了ISO10646來規範和整合字符集(被成為通用字符集 Universal Character Set,UCS )。統一碼聯盟(由各個大型企業及組織共同維護)釋出了統一碼(Unicode)專案。

  起初,UCS和Unicode各自為政,但1991年前後他們都發現:世界不需要兩個不一樣的“統一”、“通用”的字符集。所以他們聯合起來維護一個字符集,現在他們的差別大概是釋出新版本時使用什麼字型了-_-。

  UCS和Unicode都使用最大32bit來儲存字元,他們(其實是一樣的,不過還是區分一下)的碼位(字元數)有1114112個,從0x0到0x0x10FFFF。

  大家可能會奇怪,32bit最多可以表示超42億個字元(即從0x0到0xFFFFFFFF),為什麼只使用了其中這麼小一部分呢?其實,這裡面還有一些其他原因。

 

  使用32bit來儲存字元看起來是一件一勞永逸的方式,但如果這32bit是定寬的(即任何字元都要使用完這32bit)的話就不可避免的造成空間的浪費,程式效率也會降低!

  能不能把UCS(Unicode)設計成“變寬”的呢?聰明的設計師想到了一個主意,他們發明了一種名為“統一碼轉換格式”即UTF的來將字元對應的數字(可能從小於127至大於100萬不等)轉化為多個位元組來進行儲存。

  簡單說來,UCS或Unicode只是定義了從0到1114112這些數字各自是什麼字元(而指示介面上該怎麼顯示這個字元則是由“字型”來管理,比如Windows下“微軟雅黑”字型就是這個樣子的(部分):

  

)。而如果從1~4個位元組(變寬)還原出這個數字(或字元)就是UTF的事兒了。

  比如“漢”字,對應數字為23383,那麼要使用3位元組的UTF8格式位元組進行儲存(原因我們下面再講),或者1位元組的UTF16或1位元組的UTF32。

  “漢” UTF8 = {0xE6, 0xB1, 0x89}

    UTF16 = {0x6c49}

    UTF32 = {0x6c49}(高位補0可省略)

  Unicode字符集的劃分大概有兩種:按“單元(Cell)”劃分(Unicode官方文件是這樣分的)和按“平面(Plane)”劃分。一個單元為128個字元,一個平面有65536個字元。

  我們通常使用的一個平面取值為0x0到0xFFFF,這個平面被成為BMP(Basic Multilingual Plane)即“平面0”。由於這個平面只是用兩個位元組就可以完整表示,字符集又可以成為UCS-2(即使用2字元的“通用字符集”,UCS-4即為4位元組,將UCS-4的高兩位去除即為UCS-2)。

  我們常用的27973個漢字都存放與平面0上,整個Unicode共定義了71226個漢字(Unicode5.0.0),平面2的43253個字元都是漢字。

  

  UTF之間的關係和轉換。上文說道,“漢”字需要3個UTF8位元組來儲存,這是因為要符合UTF格式的規範。

  一個模版可以告訴我們UTF8是怎樣儲存資料的:

Unicode編碼(16進位制) 
UTF-8 位元組流(二進位制)
000000 - 00007F
0xxxxxxx
000080 - 0007FF
110xxxxx 10xxxxxx
000800 - 00FFFF
1110xxxx 10xxxxxx 10xxxxxx
010000 - 10FFFF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

  可以看到,如果使用兩個位元組來儲存資料,UTF8最多可以儲存2^11個字元,最大的數字為0x7FF即2047,顯然無法存下數字為23383的“漢”字。

  而通過模版我們也可以看出Unicode的最大bit數為21。

 

  數字向UTF的轉換也很簡單了,把數字換成二進位制(不足21位的高位補0),然後填入對應UTF的模版(UTF16和UTF32的模版大家請自行檢視,LE和BE區別在於高位和低位的位置,Windows和Linux為LE,MacOS為BE)中替換xxxxxxx就行了!很簡單吧。

  UTF在檔案中的儲存。UTF格式在檔案中總有固定檔案頭:

UTF編碼
Byte Order Mark
UTF-8
EF BB BF
UTF-16LE
FF FE
UTF-16BE
FE FF
UTF-32LE
FF FE 00 00
UTF-32BE
00 00 FE FF

 

  如“漢”字在檔案中的儲存(不包括頭):

Unicode編碼
UTF-16LE 
UTF-16BE 
UTF32-LE 
UTF32-BE
0x006C49
49 6C
6C 49
49 6C 00 00
00 00 6C 49

  

  各個系統和語言對Unicode的支援:

    Windows NT從底層支援Unicode(不幸的是,Windows 98只是小部分支援Unicode)。先天即被ANSI束縛的C程式設計語言通過對寬字元集的支援來支援Unicode。

    Windows底層使用UTF16,Linux使用UTF32(未考證)。

    C#和Java支援UTF16且是預設行為(如字串天生為UTF16格式字元陣列,Java還可以使用'\uxxxx'格式宣告一個字元)。

    XML及其子集HTML對UTF16支援很好,為跨平臺你可以使用'&#xxxx;'來宣告一個字元。

 

 

 歡迎您移步我們的交流群,無聊的時候大家一起打發時間:Programmer Union

 或者通過QQ與我聯絡:點選這裡給我發訊息

 (最後編輯時間2013-09-17 20:59:38)

 

相關文章