田海立@CSDN
2012-04-25
本文主要討論Unicode的編碼及其各種實現,著重討論UTF-16,UTF-8的實現規則,以及Big-endian和Little-Endian的儲存順序。
一、Unicode編碼
Unicode出現之前已經有各種編碼通訊協定:ANSI、ISO8859-1、GB2312、GBK以及BIG-5等。Unicode試圖統一各種編碼,在Unicode演化過程中,也有自身不斷修複的過程:剛開始的時候用16位表達65535個字元,認為已經足夠收集所有的字元;後來隨著大量中文、韓文和日文等表意文字的加入,已經超出了65535個字元,16位已經不能描述所有的字元集了。
在Unicode字元集中的某個字元對應的代碼值,稱作代碼點(Code Point),用16進位書寫,並加上U+首碼。比如,‘田’的代碼點是U+7530;‘A’的代碼點是U+0041。
Unicode定義的字元集已經超過16位所能表達的範圍,把所有這些CodePoint分成17個代碼平面(Code Plane):
- U+0000 ~ U+FFFF劃入基本多語言平面(Basic MultilingualPlane,簡記為BMP);
- 其餘劃入16個輔助平面(Supplementary Plane),代碼點範圍U+10000 ~ U+10FFFF。
雖然這樣劃分,但並不是每個Plane中的Code point都對應有字元,這裡面有保留的,還有特殊用途的。
二、Unicode編碼的實現
Unicode的實現方式不同於編碼方式。一個字元的Unicode編碼是確定的,但是在實際儲存和傳輸過程中,由於不同系統平台的設計不一定一致,以及出於節省空間的目的,對Unicode編碼的實現方式有所不同。Unicode的實現方式稱為Unicode轉換格式(Unicode Transformation Format,簡稱為UTF)。
對Unicode編碼的主要有UTF-16BE、UTF-16LE、UTF-8、UTF-7以及UTF-32等實現方式,目前常用的實現方式是UTF-16LE、UTF-16BE和UTF-8。
2.1 UTF-16
UTF-16是用16bit編碼來表達Unicode,這樣表達範圍是216(即65536),也就是UTF-16的代碼單元(Code Unit)為16bits。如果表達BMP內的字元,用一個UTF-16的Code Unit就可表達,對於輔助平面內的字元,UTF-16有巧妙的設計。
落在BMP內,從U+D800到U+DFFF之間的Code Point區段是持續保留不映射到字元, UTF-16利用這保留下來的0xD800-0xDFFF區段的CodePoint來對輔助平面內的字元的Code Point進行編碼。
對U+0000.. U+D7FF以及U+E000.. U+FFFF的編碼
UTF-16與UCS-2對這個範圍內的CodePoint進行編碼,採用單個16bit長的CodeUnit,數值等價於對應的Code Point。BMP中的這些Code Point是僅有的可以被UCS-2表示的Code Point。
對U+10000.. U+10FFFF的編碼
輔助平面(Supplementary Planes)中的CodePoint,在UTF-16中被編碼為一對16bit長的Code Unit(即32bit,4Bytes),稱作代理對(surrogate pair)。
具體方法是:
- Code Point減去0x10000, 得到的值是長度為20bit(0..0xFFFFF);
- 步驟1得到數值的高位的10位元的值(值範圍為0..0x3FF)被加上0xD800得到第一個Code Unit或稱作高位代理(high surrogate)或前置代理(lead surrogate)。取值範圍是0xD800..0xDBFF。
- 步驟1得到數值的低位的10位元的值(值範圍為0..0x3FF)被加上0xDC00得到第二個Code Unit或稱作低位代理(low surrogate)或後尾代理(trail surrogate)。取值範圍是0xDC00..0xDFFF。
這樣,這個範圍內的字元就被編碼成了一個代理對[lead surrogate,trail surrogate]:兩個16bits的Code Unit,取值範圍分別是0xD800..0xDBFF和0xDC00..0xDFFF。而BMP中得到的Code Unit的範圍是0x0000..0xFFFF(0xD800..0xDFFF是保留的,不包含其中),所以這三個區段是相互不重疊的,在解碼時很容易實現。
UTF-16解碼[高位代理+低位代理]得到的Code Unit對與Code Point的對應關係如下表所示。
UTF-16解碼 |
hi \ lo |
DC00 |
DC01 |
… |
DFFF |
D800 |
10000 |
10001 |
… |
103FF |
D801 |
10400 |
10401 |
… |
107FF |
⋮ |
⋮ |
⋮ |
⋱ |
⋮ |
DBFF |
10FC00 |
10FC01 |
… |
10FFFF |
下面以對U+64321的UTF-16編碼為例,看一下對於輔助平面內的字元是如何編碼的:
V = 0x64321Vx = V - 0x10000 = 0x54321 = 01010100 0011 0010 0001 Vh = 01 0101 0000 // Vx 的高位部份的 10 bitsVl = 11 0010 0001 // Vx 的低位部份的 10 bitsw1 = 0xD800 // 結果的前16位元初始值w2 = 0xDC00 // 結果的後16位元初始值 w1 = w1 | Vh = 1101 1000 0000 0000 | 01 0101 0000 = 1101 1001 0101 0000 = 0xD950 w2 = w2 | Vl = 1101 1100 0000 0000 | 11 0010 0001 = 1101 1111 0010 0001 = 0xDF21
所以,這個字 U+64321 最終的 UTF-16 編碼是:
0xD950 0xDF21
UTF-16的Code Unit是16bits,兩個位元組。儲存一個Code Unit的時候,還有存取的先後順序問題,也就是Endian問題,這在後面章節講述。
2.2 UTF-8
UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字元編碼,使用一至四個位元組為每個字元編碼:
- Unicode範圍為U+0000..U+007F 的128個ASCII字元只需一個位元組編碼;
- Unicode範圍為U+0080..U+07FF的字元需要二個位元組編碼;
- Unicode範圍為U+0800..U+FFFF的其他BMP中的字元(這包含了大部分常用字)使用三個位元組編碼;
- Unicode 輔助平面的字元(其他極少使用的字元)使用四位元組編碼。
對上述提及的第四種字元而言,UTF-8使用四個位元組來編碼似乎太耗費資源了。但UTF-8對所有常用的字元都只用三個位元組表達,而且UTF-16編碼對前述的第四種字元同樣需要四個位元組來編碼,而如果是ASCII居多的字元,UTF-8能極大的節約儲存空間。UTF-8逐漸成為電子郵件、網頁及其他儲存或傳送文字的應用中,優先採用的編碼。互連網工程工作小組(IETF)要求所有互連網協議都必須支援UTF-8編碼。互連網郵件聯盟(IMC)建議所有電子郵件軟體都支援UTF-8編碼。
對CodePoint各個範圍內的字元進行UTF-8編碼的規則如下:
Code point |
UTF-8位元組流 |
U+00000000 – U+0000007F |
0xxxxxxx |
U+00000080 – U+000007FF |
110xxxxx 10xxxxxx |
U+00000800 – U+0000FFFF |
1110xxxx 10xxxxxx 10xxxxxx |
U+00010000 – U+001FFFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
其中,U+D800到U+DFFF之間的區段在Unicode字元集的定義中沒有具體字元使用的,被用來在UTF-16編碼中對輔助平面的字元進行編碼。
下面以“田”(Code Point為U+7530)為例,看如何對其進行UTF-8編碼:
- U+7530落在U+0800..U+FFFF區間,採用三位元組編碼;
- 0x7530轉換為二進位為111
010100 110000;
- 代入表中,得到111001111001010010110000;
這樣,得到“田”(U+7530)的UTF-8編碼:0xE7 94 B0。
知道UTF-8的編碼規則,我們可以對於UTF-8編碼中的任意位元組B,進行下面解碼:
- 如果B的第一位為0,則B為ASCII碼,並且B獨立的表示一個字元;如果B的第一位為1,第二位為0,則B為一個非ASCII字元(該字元由多個位元組表示)中的一個位元組,並且不為字元的第一個位元組編碼(字元的第一個位元組之外的後編碼);
- 如果B的前兩位為1,第三位為0,則B為一個非ASCII字元(該字元由多個位元組表示)中的第一個位元組,並且該字元由兩個位元組表示;
- 如果B的前三位為1,第四位為0,則B為一個非ASCII字元(該字元由多個位元組表示)中的第一個位元組,並且該字元由三個位元組表示;
- 如果B的前四位為1,第五位為0,則B為一個非ASCII字元(該字元由多個位元組表示)中的第一個位元組,並且該字元由四個位元組表示。
2.3 UCS-2 vs UTF-16,UCS-4 vs UTF-32
UCS-2每個字元佔用2個位元組。UCS-2是UTF-16的子集。在沒有輔助平面前,UTF-16與UCS-2所指的是同一的意思。但當引入輔助平面字元後,UTF-16加入了對輔助平面內的字元的支援。現在若有軟體聲稱自己支援UCS-2編碼,那其實是暗指它不支援UTF-16中超過2bytes的字集。亦即,對於小於0x10000的UCS碼,UTF-16編碼就等於UCS碼。Java早期版本對Unicode的支援,就只是UCS-2的支援,現在加入了對UTF-16的完整支援。
UCS-4與UTF-32的意義一致,對每個字元都使用4位元組(31位字元集,加上恒為0的首位,共需佔據32位)。理論上最多能表示231個字元,完全可以涵蓋一切語言所用的符號。雖然每一個Code Point使用固定長定的位元組看似方便,對於普通只需要2個位元組儲存的常用字占絕大對數的字元集來說,卻極大的浪費了空間,並沒怎麼得到應用。
三、Big-Endian/Little-Endian與BOM
在講UTF-16編碼方式時說到,UTF-16編碼的Code Unit是2個位元組,這兩個位元組在傳輸和預存程序中,高/低位位置不同,是不同的字元。比如,“田”的UTF-16編碼是0x7530,但是如果存成0x3075,就變成了“ふ”,成了另外的字元。
所以,為了識別一個編碼過的字元的儲存順序,必須用特殊字元來指示。Unicode字元中U+FEFF被用來指示這種儲存順序,被稱作Byte Order Mark(BOM)。
- Big-Endian:最低位地址存放高位位元組,可稱高位優先,記憶體從最低地址開始按順序存放(高數位元字先寫)。最高位位元組放最前面。
- Little Endian:最低位地址存放低位位元組,可稱低位優先,記憶體從最低地址開始按順序存放(低數位元字先寫)。最低位位元組放最前面。
BOM在Big-Endian系統上儲存為FE FF;而在Big-Endian系統上儲存則為FF FE。所以在以Big-Endian儲存的UTF-16(UTF-16BE)的檔案的開頭,用FEFF指示;以Little-Endian儲存的UTF-16(UTF-16LE)的檔案的開頭,用FFFE指示。
BOM的UTF-8編碼為11101111 1011101110111111 (EF BB BF),所以一般EF BB BF被放在文本的開頭,用來指示其編碼為UTF-8。
四、Unicode編碼實踐
在Windows的文本編輯工具記事本上,選擇“另存新檔”的時候,使用者可以選擇不同的編碼選項,對應編碼選項有“ANSI”,“Unicode”,“Unicode big endian”,以及“UTF-8”。因為Windows的儲存方式是Little-Endian,所以“Unicode”,“Unicode big endian”對應的分別是UTF-16LE和UTF-16BE。
讀者可以試著編寫一串字元,然後分別用不同的編碼儲存,再用可以16進位編寫的純文字編輯工具(如,Ultra-edit)來檢驗一下具體的編碼實現和儲存順序。下面是筆者將“田海立(U+7530, U+6D77, U+7ACB)”以不同編碼方式儲存,得到的結果:
田海立_UTF-16BE.txt
FEFF75306D777ACB
田海立_UTF-16LE.txt
FFFE3075776DCB7A
田海立_UTF-8.txt
EFBBBFE794B0E6B5B7E7AB8B
為了明確起見,BOM的編碼用粗體標註;田的編碼用紅色標註;海的編碼用綠色標註;立的編碼用藍色標註。可以看到,記事本(Notepad)儲存的Unicode編碼的檔案的開頭位置,用BOM的相應編碼指示了編碼格式。
【後記】曆史
最近需要用到Unicode的編碼實現方式,又收集了一下資料。發現早在06年的時候,筆者就準備總結一下Unicode的編碼實現,文檔裡也已經有了提綱。現在也不記得當時什麼原因給耽擱了,好在現在及時總計歸納。好腦子不如爛筆頭啊。如果當初總結下來,現在也不用再浪費時間收集資料。
希望,這次的總結能比較完善,以後再用到Unicode編碼,只要參考此文即可!(當然前提是Unicode標準別又演化了^_^)
【附】基本概念對照
- Code Point代碼點或碼位
- Code Unit代碼單元或碼元,是指一個已編碼的文本中具有最短的位元組合的單元。對於UTF-8來說,碼元是8位元長;對於UTF-16來說,碼元是16位元長;對於UTF-32來說,碼元是32位元長。
- BMP - Basic Multilingual Plane
- UTF - Unicode Transformation Format
- BOM – Byte Order Mark
- UCS - Universal Character Set
轉載自:
http://blog.csdn.net/thl789/article/details/7506133