刨根究底字符編碼之十一——UTF-8編碼方式與字節序標記
UTF-8編碼方式與字節序標記
一、UTF-8編碼方式
1.
接下來將分別介紹Unicode字符集的三種編碼方式:UTF-8、UTF-16、UTF-32。這裏先介紹應用最為廣泛的UTF-8。
為滿足基於ASCII、面向字節的字符處理的需要,Unicode標準中定義了UTF-8編碼方式。UTF-8應該是目前應用最廣泛的一種Unicode編碼方式(但不是最早面世的,UTF-16要早於UTF-8面世)。它是一種使用8位碼元(即單字節碼元)的變寬(即變長或不定長)碼元序列的編碼方式。
由於UTF-16對於ASCII字符也必須使用兩個字節(因為是16位碼元)進行編碼,存儲和處理效率相對低下,並且由於ASCII字符經過UTF-16編
因此,UTF-16一開始推出的時候就遭到很多西方國家的抵制,大大影響了Unicode的推行。於是後來又設計了UTF-8編碼方式,才解決了這些問題。
2.
UTF-8的碼元由8位單字節組成;在UTF-8中,因為碼元較小的緣故,Unicode碼點值被映射到一個、兩個、三個或四個碼元;換言之,UTF-8使用一個至四個8位單字節碼元的序列來表示Unicode字符。
UTF-8編碼方式對所有ASCII碼點值(0x00~0x7F)具有透明性。所謂透明性,具體指的是在U+0000到U+007F範圍內(十進制為0~127)的Unicode碼點值,被直接轉換為UTF-8單一字節碼元0x00~0x7F,與ASCII碼沒有區別。
並且,0x00~0x7F不會出現在UTF-8編碼的非ASCII字符的首字節與非首字節的任意一個字節中(非ASCII字符的UTF-8編碼為由多個單字節碼元所組成的碼元序列),這樣就保證了與早已應用廣泛且已成為工業標準的ASCII碼的完全兼容,避免了歧義,同時糾錯能力也強。
3.
UTF-8同其他的多字節碼元編碼方式相比具有以下優點:
a) UTF-8的編碼空間足夠大,未來Unicode新標準收錄更多字符,UTF-8也能適應,因此不會再出現UTF-16那樣的尷尬。
(註:這裏所指的編碼空間並不是前文所提到的編號空間Code Space,編號空間屬於編號字符集CCS裏的概念,而編碼空間屬於字符編碼方式CEF裏的概念,兩者不能等同;這裏的編
b) UTF-8是變長編碼(準確地說是變長碼元序列,而碼元本身是固定長度為8位單字節的,也就是說,UTF-8采用的單字節碼元),比如一個字節足以容納所有的ASCII碼字符,就用一個字節來存儲,不必在高位補0以浪費更多的字節來存儲,因此在英語作為國際語言的現實情況下,UTF-8因其ASCII字符的單字節編碼這一特性可節省空間。
c) UTF-8完全直接兼容ASCII碼,而非不完全間接兼容。
d) UTF-8的碼元序列的第一個字節指明了後面所跟的字節的數目(即帶有前綴碼),這對字節流的前向解析非常有效(詳見後面的附文《UTF-8是怎麽編碼的——UTF-8的編碼算法介紹》)。
e) 也因為UTF-8編碼帶有前綴碼,所以容錯性好,即使在傳輸過程中發生局部的字節錯誤,比如即便丟失、增加、改變了某些字節,也不會導致所有後續字符全部錯亂這樣傳遞性、連鎖性的錯誤問題(否則,若存在錯誤傳遞性、連鎖性的話,一旦中間某些字節出錯,則必須丟棄從出錯點開始到結尾的所有編碼字節,比如GB碼、UTF-16碼就是如此),因此很容易重新同步,具有很強的魯棒性(即健壯性)。
f) 由於UTF-8編碼沒有狀態,從UTF-8字節流的任意位置開始可以有效地找到一個字符的起始位置,字符邊界很容易界定、檢測出來,所以具有很好的“自同步性”。
g) UTF-8已經成為互聯網所采用的字符編碼的事實標準。
h) UTF-8是字節順序無關的(因為是單字節碼元,而非像UTF-16、UTF-32這樣的多字節碼元),它的字節順序在所有系統中都是一樣的,其碼元序列與字節序列(字節流)相同,因此它實際上並不需要字節順序標記BOM(Byte-Orde Mark),雖然Windows系統經常“多此一舉”地加上BOM。(有關字節序標記BOM的介紹見下文)
字節序問題在進行信息交換時會帶來不小的麻煩。如果字節序未協商好,將導致亂碼;若協商結果為雙方一個采用大端一個采用小端,則必然有一方要進行大小端轉換,性能損失不可避免(字節序的大小端問題其實不像看起來那麽簡單,有時會涉及硬件、操作系統、上層應用軟件多個層次,可能會導致多次轉換,詳見前文中有關字節序Byte-Orde的介紹)。
i) 字節FE(二進制為1111 1110)和FF(二進制為1111 1111)在UTF-8編碼中永遠不會出現(因為UTF-8編碼方式中,每個字節只能以0、110、1110、11110或10開頭,詳見下面附文)。因此可以用稱之為零寬度不中斷空格(ZERO WIDTH NO-BREAK SPACE)的字符(Unicode字符名稱為U+FEFF)作為字節順序標記BOM來標明UTF-16或UTF-32文本的字節序。
(Windows系統中BOM有時也用在UTF-8編碼的文本文件的開頭,雖然UTF-8編碼不存在字節序問題,但Windows卻用BOM來表明該文本文件的編碼格式為UTF-8,看起來這有點“多此一舉”,其具體原因詳見後文)
j) UTF-8編碼可以通過屏蔽位和移位操作快速讀寫。
k) 字符串比較時strcmp()和wcscmp()的返回結果相同,因此使排序變得更加容易。
4.
UTF-8編碼方式也並非完美無缺,大致上有如下缺點:
a) 無法根據字符數直接判斷出UTF-8文本的字節數,因為UTF-8是一種變長編碼方式(碼元雖然固定為8位單字節,但碼元序列是變長的,可能是單個碼元共8位,比如ASCII字符;也可能是兩個碼元共16位、三個碼元共24位、四個碼元共32位等)。因此,無論是計算字符數,還是執行索引操作,效率都不高。
b) 需要用2個字節編碼那些在擴展ASCII(即EASCII)字符集中只需1個字節編碼的擴展字符。
c) 以8位單字節碼元編碼的UTF-8字符會被Email網關過濾,因為Internet上的信息傳輸最初設計為7位ASCII碼字符(ASCII僅用到了1個字節的低7位)的傳輸。因此產生了UTF-7編碼(類似於同樣為Email傳輸而設計的Base64編碼或quoted-printable編碼,由於Base64編碼或quoted-printable編碼各有其不足,因此又設計了UTF-7編碼)。
d) UTF-8在它的表示中使用值100xxxxx的幾率超過50%,而現存的實現如ISO 2022、4873、6429和8859系統,會把它錯認為是C1控制碼。因此產生了UTF-7.5編碼。
二、字節序標記BOM
1.
在將邏輯形式的碼元序列(或可稱之為邏輯編碼)映射為物理形式的字節序列(或可稱之為物理編碼)時,因系統平臺的差異,存在一個字節序(Byte-Order字節順序)的問題。Unicode/UCS規範中推薦的標記字節順序的方法是BOM字節序標記(Byte-Order Mark字節順序標記)。
字節序標記BOM是Unicode碼點值為FEFF(十進制為65279,二進制為1111 1110 1111 1111)的字符的別名。
最初,字符U+FEFF如果出現在字節流的開頭,則用來標識該字節流的字節序——是高位在前還是低位在前;如果它出現在字節流的中間,則表達為該字符的原義——零寬度不中斷空格(ZERO WIDTH NO-BREAK SPACE零寬度無斷空白)。該字符名義上是個空格,實際上是零寬度的,即相當於是不可見也不可打印字符(平常使用較多的是ASCII空格字符,是非零寬度的,需要占用一個字符的寬度,為可見不可打印字符)。
從Unicode 3.2開始,U+FEFF只能出現在字節流的開頭,且只能用於標識字節序,就如它的別名——字節序標記——所表示的意思一樣;除此以外的用法已被舍棄。取而代之的是,使用U+2060來表示零寬度不中斷空格。
2.
如果UTF-16編碼的字節序列為大端序,則該字節序標記在字節流的開頭呈現為0xFE 0xFF;若字節序列為小端序,則該字節序標記在字節流的開頭呈現為0xFF 0xFE。如果UTF-32編碼的字節序列為大端序,則該字節序標記在字節流的開頭呈現為0x00 0x00 0xFE 0xFF;若字節序列為小端序,則該字節序標記在字節流的開頭呈現為0xFF 0xFE 0x00 0x00。
UTF-8編碼本身沒有字節序的問題,但仍然有可能會用到BOM——有時被用來標示某文本是UTF-8編碼格式的文本;再強調一遍:在UFT-8編碼格式的文本中,如果添加了BOM,則只用它來標示該文本是由UTF-8編碼方式編碼的,而不用來說明字節序,因為UTF-8編碼不存在字節序問題。
3.
許多Windows程序(包含記事本)會添加BOM到UTF-8編碼格式的文件中(至於為什麽要添加BOM,可參看後續《微軟跟聯通有仇?》一文)。然而,在類Unix系統中,這種作法則不被建議采用。
因為它會影響到無法識別它的編程語言,如gcc會報告源碼文件開頭有無法識別的字符;而在PHP中,如果沒有激活輸出緩沖(output buffering),它會使得頁面內容開始被送往瀏覽器(即header頭被提交),這使PHP腳本無法指定header頭(HTTP Header)。
對於已在IANA註冊的字符編碼(實際為字符編碼模式CES)UTF-16BE、UTF-16LE、UTF-32BE和UTF-32LE等來說,不可使用BOM。因為其名稱本身已決定了其字節順序。對於已註冊的字符編碼UTF-16和UTF-32來說,則必須在文本開頭使用BOM。
4.
不同編碼的字節序列中所使用的字節序標記BOM本身的字節序列呈現:
三、小結
1.
由於UTF-8編碼方式以一個字節(8位)作為碼元,屬於單字節碼元,在計算機處理、存儲和傳輸時不存在字節序問題(字節序問題只跟多字節碼元有關),因此避免了平臺依賴性,跨平臺兼容性好。
它相對於其他編碼方式對英語更為友好,同樣也對計算機語言(如C++、Java、C#、JavaScript、PHP、HTML等)更為友好。它在處理ASCII等常用字符集時很少會比UTF-16低效。
2.
所以,UTF-8是較為平衡、較為理想的Unicode編碼方式。雖然Windows平臺由於歷史的原因API缺乏對UTF-8的原生支持(Windows原生支持的是UTF-16,因為UTF-16早於UTF-8面世),導致UTF-8推出後的早期使用不廣,但目前是應用最為廣泛的三大UTF編碼方式之一。
因此,應該盡量使用UTF-8(準確地說,應該盡量使用UTF-8 without BOM,即不帶字節順序標記BOM的UTF-8)。
(未完待續)
【預告:本《刨根究底字符編碼》系列的下一篇將重點剖析UTF-8究竟是怎麽編碼的(即UTF-8的編碼算法介紹);而《刨根究底正則表達式》系列的下一篇為正則表達式簡介(上),包括正則表達式的概念、功能、簡史、流派介紹,敬請關註!】
刨根究底字符編碼之十一——UTF-8編碼方式與字節序標記