淺析Unicode編碼

1.緣起
   在學習java的過程中常常出現UTF-8、UTF-16等等的編碼,而書中又沒有給出相應的解釋,這樣就促使我去研究一下這些編碼。

2.big- endian和little-endian
在談編碼之前,讓我們先了解幾個概念,即big-endian和little-endian。
 “endian” 這個詞出自《格列佛遊記》。小人國的內戰就源於吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾 發生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。
    在計算機中,big-endian和little-endian是cpu處理多字節數的不同方式。big-endian即高位數放在前,這和我們平時的閱 讀習慣一樣。反之,little-endian是高位放在後面。
    舉個例子來說,"A"的Unicode編碼爲0x0041,則在文件中big-endian的順序爲00 41,而little-endian的順序爲41 00。

3.Unicode、UCS和UTF
和我們熟知的ASCII碼一 樣,IS08859、Unicode和UCS(Universal Character Set)均是字符表,每個字符對應一個唯一的字符號(Character Number)。不同的僅是它們的字符集大小的不同。他們的大小關係是:ASCII<IS08859<Unicode<UCS。且這些 字符表是向下兼容的,即同一個字符在這些方案中總是有相同的編碼,後面的標準支持更多的字符。注意這裏我們說的Unicode是指Unicode1.0 版。那麼何謂Unicode1.0版,它是否還有其他版本呢?
那麼我們就需要了解一下Unicode的歷史:歷史上存在兩個試圖獨立設計 Unicode的組織,即國際標準化組織(ISO)和一個軟件製造商的協會(unicode.org)。ISO開發了ISO-10646項目,也即我們通 常所說的UCS,而Unicode協會開發了Unicode項目。其中ISO-10646的code space爲U+0000-U+10FFFF,而Unicode的code space爲U+0000-U+FFFF,這就是Unicode1.0,也是我們平時說的Unicode。
在1991年前後,雙方都認識到世界不 需要兩個不兼容的字符集。於是它們開始合併雙方的工作成果,併爲創立一個單一編碼表而協同工作。於是誕生了Unicode2.0, 它採用了與ISO 10646-1相同的字庫和字碼。
UCS規定了怎麼用多個字節表示各種文字。而怎樣傳輸這些編碼,是由UTF(UCS Transformation Format)規範規定的,常見的UTF規範包括UTF-8、UTF-7、UTF-16。由於java中主要使用UTF-8、UTF-16,故我們主要介 紹這兩種編碼,順帶提及UTF-32,UCS-2,UCS-4。
4. UTF-16
我們先來談談UTF-16,這也是JDK5.0中 char類型的編碼方式。首先我們定義:
Code Point:在ISO-10646編碼機制中,字符對應的整數值稱爲Code Point。
在 Unicode標準中,code point的書寫方式是:U+“16機制數”。如A的Unicode編碼爲U+0041。ISO-10646(即Unicode2.0標準)的code space爲U+0000-U+10FFFF,它的code space可被分爲17個group,每個group有65536個code point,第一個group稱爲basic multilingual plane,即爲Unicode1.0標準定義的字符集合,範圍從U+0000到U+FFFF。而code point在U+10000-U+10FFFF之間的字符被稱爲supplementary characters。
我們再定義:
Code Unit:code point在basic multilingual plane範圍內用16bit表示的字符稱爲code unit.
顯 然在basic multilingual plane中的字符用一個code unit即可表示。那麼如何表示supplementary characters呢?
我們首先需要知道一個背景:在basic multilingual plane的U+0000到U+FFFF的範圍內有一段保留區範圍(從U+D800到U+DFFF),其中未定義任何字符,這個區域稱爲 surrogates area。supplementary characters是由兩個code unit來表示,我們讓U表示某一supplementary characters的code point,讓U’= U-0x10000,因U<= 0x10FFFF,故U’<= 0xFFFFF剛好20bits,然後在兩個code unit中各保存10bits。那麼如何來放這10bits呢?UTF-16標準規定第一個code unit的範圍是U+D800--U+DBFF,第二個code unit的範圍是U+DC00-- U+DFFF。在編碼時將兩個16bits的整數初始化爲0xD800和0xDC00,然後將20bits中的高10bits放到第一個code unit的低10bit中,20bits中的低10bits放到第二個code unit的低10bit中,這樣便完成了supplementary characters的編碼工作。
那麼將ISO 10646 Character Number編碼爲UTF-16的過程爲:
Let U be the character number, no greater than 0x10FFFF.
1) If U < 0x10000, encode U as a 16-bit unsigned integer and
      terminate.

   2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
      U' must be less than or equal to 0xFFFFF. That is, U' can be
      represented in 20 bits.

   3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
      0xDC00, respectively. These integers each have 10 bits free to
      encode the character value, for a total of 20 bits.

   4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
      bits of W1 and the 10 low-order bits of U' to the 10 low-order
      bits of W2. Terminate.
相應的,從 UTF-16解碼爲ISO 10646 Character Number的過程爲:
Let W1 be the next 16-bit integer in the sequence of integers representing the text. Let W2 be the (eventual) next integer following W1.
1) If W1 < 0xD800 or W1 > 0xDFFF, the character value U is the value
      of W1. Terminate.

   2) Determine if W1 is between 0xD800 and 0xDBFF. If not, the sequence
      is in error and no valid character can be obtained using W1.
      Terminate.

   3) If there is no W2 (that is, the sequence ends with W1), or if W2
      is not between 0xDC00 and 0xDFFF, the sequence is in error.
      Terminate.

   4) Construct a 20-bit unsigned integer U', taking the 10 low-order
      bits of W1 as its 10 high-order bits and the 10 low-order bits of
      W2 as its 10 low-order bits.

5) Add 0x10000 to U' to obtain the character value U. Terminate.
從這裏可以看出限定兩個code unit的範圍可以讓我快速的確定一個字符是由一個code unit組成還是兩個,而且能確定此code unit是supplementary characters的第一部分還是第二部分。
談到這裏,我們還要討論一下UCS-2,它常和UTF-16混淆。UCS-2是ISO 10646編碼規範定義的一個編碼表,它用16bits來編碼字符,但是它的code space僅爲U+0000-U+FFFF,即Basic Multilingual Plane,它不能表示supplementary characters。
5. UTF-8
UTF-8要編 碼的字符均來自範圍U+0000--U+10FFFF,這些字符用UTF-8編碼會生成1到4個“8比特組”(octect),其編碼形式爲:
Character number range(16進制)  UTF-8 octet sequence(2進制)
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

從中我們 可以看出在範圍00-7F內,ASCII碼和UTF-8的編碼一樣。除此之外,每個字符號範圍對應的UTF-8 octet sequence的第一個octet中“1”的數量等於這個sequence中octet的數量,後面的每個octet均以“10”開頭。
將一個 字符編碼爲UTF-8的過程如下:
  1.  Determine the number of octets required from the character number
       and the first column of the table above.  It is important to note
       that the rows of the table are mutually exclusive, i.e, there is
       only one valid way to encode a given character.

   2.  Prepare the high-order bits of the octets as per the second
       column of the table.

   3.  Fill in the bits marked x from the bits of the character number,
       expressed in binary.  Start by putting the lowest-order bit of
       the character number in the lowest-order position of the last
       octet of the sequence, then put the next higher-order bit of the
       character number in the next higher-order position of that octet,
       etc. When the x bits of the last octet are filled in, move on to
       the next to last octet, then to the preceding one, etc. until all
       x bits are filled in.
例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間,所以肯定要用3 字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進制是:0110 110001 001001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
相應的將UTF-8編碼解 碼的過程如下:
1.  Initialize a binary number with all bits set to 0.  Up to 21 bits
       may be needed.

   2.  Determine which bits encode the character number from the number
       of octets in the sequence and the second column of the table
       above (the bits marked x).

   3.  Distribute the bits from the sequence to the binary number, first
       the lower-order bits from the last octet of the sequence and
       proceeding to the left until no x bits are left.  The binary
       number is now equal to the character number.
6. UTF的字節序和BOM
UTF-8以字節爲編碼單元,沒有字節序的問題。UTF-16以兩個字節爲編碼單元,在解釋 一個UTF-16文本前,首先要弄清楚每個編碼單元的字節序。例如收到一個“奎”的Unicode編碼是594E,“乙”的Unicode編碼是 4E59。如果我們收到UTF-16字節流“594E”,那麼這是“奎”還是“乙”?
Unicode規範中推薦的標記字節順序的方法是BOM。 BOM是指Byte Order Mark。BOM是一個有點小聰明的想法:在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的編碼是FEFF。而FFFE在UCS中是不存在的字符,所以不應該出現在實際傳輸中。UCS規範建議我們在傳輸字節流前,先傳輸 字符"ZERO WIDTH NO-BREAK SPACE"。
  這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如 果收到FFFE,就表明這個字節流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。
   UTF-8不需要BOM來表明字節順序,但可以用BOM來表明編碼方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB BF開頭的字節流,就知道這是UTF-8編碼了。
Windows就是使用BOM來標記文本文件的編碼方式的。
7. UTF-32
要 談UTF-32,我們就不得不提UCS-4。UCS-4和UCS-2一樣,是ISO 10646定義的一個編碼表(encoding form),它用32bits來編碼字符,它的code space爲從0x0000-0x7FFFFFFF(規定最高位必須爲0)。而ISO 10646的範圍僅爲0x0000-0x10FFFF,並聲稱以後的擴展也不會超出此範圍。所以UCS-4的code space就太大了,於是就產生了UTF-32,它仍用32bits來編碼字符,但它的code space僅爲0x0000-0x10FFFF。
我 們可以看出用4個字節來表示一個字符太浪費空間了,這也是爲何這兩個編碼方式很少使用的原因,但用4個字節來表示一個字符也帶來一個好處,即所有的字符均 爲等長,而不論UTF-16還是UTF-8字符均爲變長,這就給字符處理帶來了方便。
參考文獻:
[1] 程序員趣味讀物:談談Unicode編碼
 <http://www.pconline.com.cn/pcedu/empolder/gj/other/0505/616631_1.html >
[2]  RFC 3629 - UTF-8, a transformation format of ISO 10646
 <http://www.faqs.org/rfcs/rfc3629.html >
[3] RFC 2781 - UTF-16, an encoding of ISO 10646
 <http://www.faqs.org/rfcs/rfc2781.html >
[4] 維基百科
 http://en.wikipedia.org/wiki

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章