1. 程式人生 > >刨根究底字符編碼之十一——UTF-8編碼方式與字節序標記

刨根究底字符編碼之十一——UTF-8編碼方式與字節序標記

所有 碼元 unix 找到 概念 不可見 執行 大端 位置

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

碼後得到的兩個字節,高字節始終是0x00,很多C語言的函數都將此字節視為字符串末尾從而導致無法正確解析文本。

因此,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裏的概念,兩者不能等同;這裏的

碼空間可理解為碼方式的未來可擴展性、高適應性,詳見後文《UTF-8究竟是怎麽碼的——UTF-8的編碼算法介紹》以及《UTF-16究竟是怎麽碼的——UTF-16的編碼算法介紹》)

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編碼方式與字節序標記