UTF-8編碼方式與字節(jié)序標記
一、UTF-8編碼方式
1.
接下來將分別介紹Unicode字符集的三種編碼方式:UTF-8蚜迅、UTF-16俊抵、UTF-32。這里先介紹應用最為廣泛的UTF-8徽诲。
為滿足基于ASCII吵血、面向字節(jié)的字符處理的需要,Unicode標準中定義了UTF-8編碼方式蹋辅。UTF-8應該是目前應用最廣泛的一種Unicode編碼方式(但不是最早面世的,UTF-16要早于UTF-8面世)侦另。它是一種使用8位碼元(即單字節(jié)碼元)的變寬(即變長或不定長)碼元序列的編碼方式。
由于UTF-16對于ASCII字符也必須使用兩個字節(jié)(因為是16位碼元)進行編碼褒傅,存儲和處理效率相對低下,并且由于ASCII字符經(jīng)過UTF-16編碼后得到的兩個字節(jié)殿托,高字節(jié)始終是0x00,很多C語言的函數(shù)都將此字節(jié)視為字符串末尾從而導致無法正確解析文本碌尔。
因此,UTF-16一開始推出的時候就遭到很多西方國家的抵制券敌,大大影響了Unicode的推行唾戚。于是后來又設計了UTF-8編碼方式,才解決了這些問題待诅。
2.
UTF-8的碼元由8位單字節(jié)組成叹坦;在UTF-8中,因為碼元較小的緣故卑雁,Unicode碼點值被映射到一個募书、兩個、三個或四個碼元测蹲;換言之莹捡,UTF-8使用一個至四個8位單字節(jié)碼元的序列來表示Unicode字符。
UTF-8編碼方式對所有ASCII碼點值(0x00~0x7F)具有透明性扣甲。所謂透明性篮赢,具體指的是在U+0000到U+007F范圍內(nèi)(十進制為0~127)的Unicode碼點值,被直接轉(zhuǎn)換為UTF-8單一字節(jié)碼元0x00~0x7F琉挖,與ASCII碼沒有區(qū)別启泣。
并且,0x00~0x7F不會出現(xiàn)在UTF-8編碼的非ASCII字符的首字節(jié)與非首字節(jié)的任意一個字節(jié)中(非ASCII字符的UTF-8編碼為由多個單字節(jié)碼元所組成的碼元序列)示辈,這樣就保證了與早已應用廣泛且已成為工業(yè)標準的ASCII碼的完全兼容寥茫,避免了歧義,同時糾錯能力也強矾麻。
(笨笨阿林原創(chuàng)文章纱耻,轉(zhuǎn)載請注明出處)
3.
UTF-8同其他的多字節(jié)碼元編碼方式相比具有以下優(yōu)點:
a) ?UTF-8的編碼空間足夠大芭梯,未來Unicode新標準收錄更多字符,UTF-8也能適應膝迎,因此不會再出現(xiàn)UTF-16那樣的尷尬粥帚。
(注:這里所指的編碼空間并不是前文所提到的編號空間Code Space,編號空間屬于編號字符集CCS里的概念限次,而編碼空間屬于字符編碼方式CEF里的概念,兩者不能等同柴灯;這里的編碼空間可理解為編碼方式的未來可擴展性卖漫、高適應性,詳見后文《UTF-8究竟是怎么編碼的——UTF-8的編碼算法介紹》以及《UTF-16究竟是怎么編碼的——UTF-16的編碼算法介紹》)
b) ?UTF-8是變長編碼(準確地說是變長碼元序列赠群,而碼元本身是固定長度為8位單字節(jié)的,也就是說突委,UTF-8采用的單字節(jié)碼元)冬三,比如一個字節(jié)足以容納所有的ASCII碼字符,就用一個字節(jié)來存儲敌蚜,不必在高位補0以浪費更多的字節(jié)來存儲弛车,因此在英語作為國際語言的現(xiàn)實情況下蒲每,UTF-8因其ASCII字符的單字節(jié)編碼這一特性可節(jié)省空間贫奠。
c) ?UTF-8完全直接兼容ASCII碼,而非不完全間接兼容泣特。
d) ?UTF-8的碼元序列的第一個字節(jié)指明了后面所跟的字節(jié)的數(shù)目(即帶有前綴碼)状您,這對字節(jié)流的前向解析非常有效(詳見后面的附文《UTF-8是怎么編碼的——UTF-8的編碼算法介紹》)眯分。
e) ?也因為UTF-8編碼帶有前綴碼弊决,所以容錯性好飘诗,即使在傳輸過程中發(fā)生局部的字節(jié)錯誤,比如即便丟失净响、增加夫椭、改變了某些字節(jié),也不會導致所有后續(xù)字符全部錯亂這樣傳遞性羽莺、連鎖性的錯誤問題(否則,若存在錯誤傳遞性、連鎖性的話,一旦中間某些字節(jié)出錯鱼蝉,則必須丟棄從出錯點開始到結尾的所有編碼字節(jié),比如GB碼洁奈、UTF-16碼就是如此)镀赌,因此很容易重新同步姆打,具有很強的魯棒性(即健壯性)。
f) ?由于UTF-8編碼沒有狀態(tài)痊剖,從UTF-8字節(jié)流的任意位置開始可以有效地找到一個字符的起始位置叮贩,字符邊界很容易界定、檢測出來捺萌,所以具有很好的“自同步性”。
g) ?UTF-8已經(jīng)成為互聯(lián)網(wǎng)所采用的字符編碼方式的事實標準驮配。
h) ?UTF-8是字節(jié)順序無關的(因為是單字節(jié)碼元琐旁,而非像UTF-16牺陶、UTF-32這樣的多字節(jié)碼元)怀估,它的字節(jié)順序在所有系統(tǒng)中都是一樣的,其碼元序列與字節(jié)序列(字節(jié)流)相同,因此它實際上并不需要字節(jié)順序標記BOM(Byte-Orde Mark),雖然Windows系統(tǒng)經(jīng)常“多此一舉”地加上BOM榛鼎。(有關字節(jié)序標記BOM的介紹見下文)
字節(jié)序問題在進行信息交換時會帶來不小的麻煩黄鳍。如果字節(jié)序未協(xié)商好,將導致亂碼拧晕;若協(xié)商結果為雙方一個采用大端一個采用小端蔫敲,則必然有一方要進行大小端轉(zhuǎn)換貌虾,性能損失不可避免(字節(jié)序的大小端問題其實不像看起來那么簡單袄膏,有時會涉及硬件、操作系統(tǒng)揖盘、上層應用軟件多個層次箕慧,可能會導致多次轉(zhuǎn)換斩熊,詳見前文中有關字節(jié)序Byte-Orde的介紹)。
i) ?字節(jié)FE(二進制為1111 1110)和FF(二進制為1111 1111)在UTF-8編碼中永遠不會出現(xiàn)(因為UTF-8編碼方式中淳衙,每個字節(jié)只能以0靴跛、110、1110腹鹉、11110或10開頭,詳見后文介紹)诫硕。因此可以用稱之為零寬度不中斷空格(ZERO WIDTH NO-BREAK SPACE)的字符(Unicode字符名稱為U+FEFF)作為字節(jié)順序標記BOM來標明UTF-16或UTF-32文本的字節(jié)序藕届。
(Windows系統(tǒng)中BOM有時也用在UTF-8編碼的文本文件的開頭踏兜,雖然UTF-8編碼不存在字節(jié)序問題,但Windows卻用BOM來表明該文本文件的編碼格式為UTF-8上忍,看起來這有點“多此一舉”繁成,其具體原因詳見后文)
j) ?UTF-8編碼可以通過屏蔽位和移位操作快速讀寫祠墅。
k) ?字符串比較時strcmp()和wcscmp()的返回結果相同,因此使排序變得更加容易。
4.
UTF-8編碼方式也并非完美無缺,大致上有如下缺點:
a) ?無法根據(jù)字符數(shù)直接判斷出UTF-8文本的字節(jié)數(shù),因為UTF-8是一種變長編碼方式(碼元雖然固定為8位單字節(jié)胆绊,但碼元序列是變長的跟继,可能是單個碼元共8位剩盒,比如ASCII字符纪挎;也可能是兩個碼元共16位烤蜕、三個碼元共24位泡徙、四個碼元共32位等)。因此,無論是計算字符數(shù)蜘矢,還是執(zhí)行索引操作寓搬,效率都不高镣典。
b) ?需要用2個字節(jié)編碼那些在擴展ASCII(即EASCII)字符集中只需1個字節(jié)編碼的擴展字符。
c) ?以8位單字節(jié)碼元編碼的UTF-8字符會被Email網(wǎng)關過濾,因為Internet上的信息傳輸最初設計為7位ASCII碼字符(ASCII僅用到了1個字節(jié)的低7位)的傳輸。因此產(chǎn)生了UTF-7編碼(類似于同樣為Email傳輸而設計的Base64編碼或quoted-printable編碼胆建,由于Base64編碼或quoted-printable編碼各有其不足,因此又設計了UTF-7編碼)魄懂。
d) ?UTF-8在它的表示中使用值100xxxxx的幾率超過50%,而現(xiàn)存的實現(xiàn)如ISO 2022、4873勾效、6429和8859系統(tǒng)其监,會把它錯認為是C1控制碼锌历。因此產(chǎn)生了UTF-7.5編碼。
(笨笨阿林原創(chuàng)文章,轉(zhuǎn)載請注明出處)
二、字節(jié)序標記BOM
1.
在將邏輯形式的碼元序列(或可稱之為邏輯編碼)映射為物理形式的字節(jié)序列(或可稱之為物理編碼)時实幕,因系統(tǒng)平臺的差異,存在一個字節(jié)序(Byte-Order字節(jié)順序)的問題辉川。Unicode/UCS規(guī)范中推薦的標記字節(jié)順序的方法是BOM字節(jié)序標記(Byte-Order Mark字節(jié)順序標記)集索。
字節(jié)序標記BOM是Unicode碼點值為FEFF(十進制為65279蛹含,二進制為1111 1110 1111 1111)的字符的別名。
最初咽安,字符U+FEFF如果出現(xiàn)在字節(jié)流的開頭沸伏,則用來標識該字節(jié)流的字節(jié)序——是高位在前還是低位在前姆另;如果它出現(xiàn)在字節(jié)流的中間,則表達為該字符的原義——零寬度不中斷空格(ZERO WIDTH NO-BREAK SPACE零寬度無斷空白)。該字符名義上是個空格,實際上是零寬度的,即相當于是不可見也不可打印字符(平常使用較多的是ASCII空格字符茶行,是非零寬度的,需要占用一個字符的寬度姿锭,為可見不可打印字符)伯铣。
從Unicode 3.2開始,U+FEFF只能出現(xiàn)在字節(jié)流的開頭焚鲜,且只能用于標識字節(jié)序忿磅,就如它的別名——字節(jié)序標記——所表示的意思一樣犀斋;除此以外的用法已被舍棄叽粹。取而代之的是虫几,使用U+2060來表示零寬度不中斷空格辆脸。
2.
如果UTF-16編碼的字節(jié)序列為大端序,則該字節(jié)序標記在字節(jié)流的開頭呈現(xiàn)為0xFE 0xFF状囱;若字節(jié)序列為小端序,則該字節(jié)序標記在字節(jié)流的開頭呈現(xiàn)為0xFF 0xFE袭艺。如果UTF-32編碼的字節(jié)序列為大端序叨粘,則該字節(jié)序標記在字節(jié)流的開頭呈現(xiàn)為0x00 0x00 0xFE 0xFF;若字節(jié)序列為小端序答倡,則該字節(jié)序標記在字節(jié)流的開頭呈現(xiàn)為0xFF 0xFE 0x00 0x00瘪撇。
UTF-8編碼本身沒有字節(jié)序的問題设江,但仍然有可能會用到BOM——有時被用來標示某文本是UTF-8編碼格式的文本叉存;再強調(diào)一遍:在UFT-8編碼格式的文本中,如果添加了BOM歼捏,則只用它來標示該文本是由UTF-8編碼方式編碼的瞳秽,而不用來說明字節(jié)序率翅,因為UTF-8編碼不存在字節(jié)序問題冕臭。
3.
許多Windows程序(包含記事本)會添加BOM到UTF-8編碼格式的文件中(至于為什么要添加BOM辜贵,可參看后續(xù)《微軟跟聯(lián)通有仇?》一文)托慨。然而,在類Unix系統(tǒng)中蔼紧,這種作法則不被建議采用狠轻。
因為它會影響到無法識別它的編程語言哈误,如gcc會報告源碼文件開頭有無法識別的字符蜜自;而在PHP中卢佣,如果沒有激活輸出緩沖(outputbuffering)虚茶,它會使得頁面內(nèi)容開始被送往瀏覽器(即header頭被提交)嘹叫,這使PHP腳本無法指定header頭(HTTP Header)刨肃。
對于已在IANA注冊的字符編碼(實際為字符編碼模式CES)UTF-16BE、UTF-16LE、UTF-32BE和UTF-32LE等來說杠步,不可使用BOM或粮。因為其名稱本身已決定了其字節(jié)順序捞高。對于已注冊的字符編碼UTF-16和UTF-32來說硝岗,則必須在文本開頭使用BOM辈讶。
4.
不同編碼的字節(jié)序列中所使用的字節(jié)序標記BOM本身的字節(jié)序列呈現(xiàn):
(笨笨阿林原創(chuàng)文章,轉(zhuǎn)載請注明出處)
三媳溺、小結
1.
由于UTF-8編碼方式以一個字節(jié)(8位)作為碼元悬蔽,屬于單字節(jié)碼元蝎困,在計算機處理倍啥、存儲和傳輸時不存在字節(jié)序問題(字節(jié)序問題只跟多字節(jié)碼元有關),因此避免了平臺依賴性始藕,跨平臺兼容性好伍派。
它相對于其他編碼方式對英語更為友好诉植,同樣也對計算機語言(如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 withoutBOM,即不帶字節(jié)順序標記BOM的UTF-8)狮荔。
(笨笨阿林原創(chuàng)文章晚树,轉(zhuǎn)載請注明出處)
(未完待續(xù))
【預告:本《刨根究底字符編碼》系列的下一篇將重點剖析UTF-8究竟是怎么編碼的(即UTF-8的編碼算法介紹)总滩,敬請關注!】