【转】刨根究底字符编码之十一——UTF-8编码方式与字节序标记BOM
一、UTF-8編碼方式
1.
接下來將分別介紹Unicode字符集的三種編碼方式:UTF-8、UTF-16、UTF-32。這里先介紹應用最為廣泛的UTF-8。
為滿足基于ASCII、面向字節的字符處理的需要,Unicode標準中定義了UTF-8編碼方式。UTF-8應該是目前應用最廣泛的一種Unicode編碼方式(但不是最早面世的,UTF-16要早于UTF-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是一種使用單字節碼元的變寬(即變長或不定長)碼元序列的編碼方式。
UTF-8編碼方式對所有ASCII碼點值(0x00~0x7F)具有透明性。所謂透明性,具體指的是在U+0000到U+007F范圍內(十進制為0~127)的Unicode碼點值,亦即ASCII字符的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-32碼就是如此),因此很容易重新同步,具有很強的魯棒性(即健壯性)。
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)的字符,因此BOM實際上可認為是該字符(U+FEFF)的別名。
最初,字符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)的話,則它會使得頁面內容開始被送往瀏覽器(即http header頭被提交),從而使PHP腳本無法再指定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编码方式与字节序标记BOM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【转】ABP源码分析三十二:ABP.Si
- 下一篇: 理想ONE车主高速惊魂一幕!太相信辅助驾