熱線電話:0755-23712116
郵箱:contact@shuangyi-tech.com
地址:深圳市寶安區(qū)沙井街道后亭茅洲山工業(yè)園工業(yè)大廈全至科技創(chuàng)新園科創(chuàng)大廈2層2A
(4)UTF-8
從前述內(nèi)容可以看出:無論是UCS-2/4還是UTF-16/32,一個(gè)字符都需要多個(gè)字節(jié)來編碼,這對那些英語國家來說多浪費(fèi)帶寬啊!(尤其在網(wǎng)速本來就不快的那個(gè)年代......),而且我們注意到UTF-16最少2字節(jié)和UTF-32不變4字節(jié),這肯定是不兼容ASCII碼的,由此,UTF-8產(chǎn)生了。
在UTF-8編碼中,ASCII碼中的字符還是ASCII碼的值,只需要一個(gè)字節(jié)表示,其余的字符需要2字節(jié)、3字節(jié)或4字節(jié)來表示。
UTF-8的編碼規(guī)則:
對于ASCII碼中的符號,使用單字節(jié)編碼,其編碼值與ASCII值相同。其中ASCII值的范圍為0~0x7F,所有編碼的二進(jìn)制值中第一位為0(這個(gè)正好可以用來區(qū)分單字節(jié)編碼和多字節(jié)編碼)。
其它字符用多個(gè)字節(jié)來編碼(假設(shè)用N個(gè)字節(jié)),多字節(jié)編碼需滿足:第一個(gè)字節(jié)的前N位都為1,第N+1位為0,后面N-1 個(gè)字節(jié)的前兩位都為10,這N個(gè)字節(jié)中其余位全部用來存儲Unicode中的碼位值。
現(xiàn)如今UTF-8 是互聯(lián)網(wǎng)上使用最廣的一種 Unicode 的實(shí)現(xiàn)方式,是其他兩種無可比擬的。
(5)UTF的字節(jié)序和BOM
字節(jié)序就要先補(bǔ)充一點(diǎn)知識:
碼元(code unit):是能用于處理或交換編碼文本的最小比特組合。它代表某種編碼中最小的可用來識別一個(gè)合法字符的最小字節(jié)數(shù)序列。
UTF-8使用變長的字節(jié)序列來表示字符;某個(gè)字符(對應(yīng)一個(gè)碼點(diǎn))可能使用1-4個(gè)字節(jié)才能表示;在UTF-8中一個(gè)字符最小可能一個(gè)字節(jié),所以我們規(guī)定1個(gè)字節(jié)就是一個(gè)碼元;
UTF-16使用也變長字節(jié)序列來表示字符;某個(gè)字符(對應(yīng)一個(gè)碼點(diǎn))可能使用2個(gè)或者4個(gè)字符來表示;因?yàn)?個(gè)字節(jié)序列是最小的能夠識別一個(gè)碼點(diǎn)的單位,同理我們規(guī)定2個(gè)字節(jié)就是一個(gè)碼元;
UTF-32使用定長的4個(gè)字節(jié)表示一個(gè)字符;一個(gè)字符(對應(yīng)一個(gè)碼點(diǎn))使用4個(gè)字符來表示,這樣4個(gè)字節(jié)就是一個(gè)碼元。
簡單來說,就是“碼點(diǎn)”經(jīng)過映射后得到的二進(jìn)制串的轉(zhuǎn)換格式單位稱之為“碼元”。“碼點(diǎn)”就是一串二進(jìn)制數(shù),“碼元”就是切分這個(gè)二進(jìn)制數(shù)的方法。這些編碼每次處理一個(gè)碼元,你可以把它理解為UTF-8每次讀碼點(diǎn)的8位,UTF-16每次讀碼點(diǎn)的16位,UTF-32每次讀碼點(diǎn)的32位,。當(dāng)然這也是為什么叫這些叫Unicode轉(zhuǎn)換格式的原因。處理的是同一個(gè)字符集,但是處理方式不同。
三
字節(jié)序
UTF-8一次一個(gè)UTF-8碼元,即處理一個(gè)字節(jié),沒有字節(jié)序的問題。UTF-16一次處理一個(gè)UTF-16碼元,對應(yīng)兩個(gè)字節(jié),UTF-32一次一個(gè)UTF-32碼元,對應(yīng)處理四個(gè)字節(jié),所以這就要考慮到一個(gè)字節(jié)序問題。
以UTF-16w為例,在解釋一個(gè)UTF-16編碼文本前,首先要弄清楚每個(gè)編碼單元的字節(jié)序。例如收到一個(gè)“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。
如果我們收到UTF-16字節(jié)流“594E”,那么這是“奎”還是“乙”?這就考慮大小端問題,所以UTF-16編碼包括三種:UTF-16BE(Big Endian),UTF-16LE(Little Endian)、UTF-16(類似的名稱UCS-2BE和UCS-2LE用于顯示UCS-2的版本。)
UTF-16BE和UTF-16LE好理解,直接指定了字節(jié)序(大小端),但是UTF-16怎么處理呢?
Unicode規(guī)范中推薦的標(biāo)記字節(jié)順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一個(gè)有點(diǎn)小聰明的想法:
在UCS編碼中有一個(gè)叫做"ZERO WIDTH NO-BREAKSPACE"的字符,它的編碼是FEFF。而FFFE在UCS中是不存在的字符,所以不應(yīng)該出現(xiàn)在實(shí)際傳輸中。UCS規(guī)范建議我們在傳輸字節(jié)流前,先傳輸字符"ZERO WIDTH NO-BREAK SPACE"。這樣如果接收者收到FEFF,就表明這個(gè)字節(jié)流是Big-Endian的;如果收到FFFE,就表明這個(gè)字節(jié)流是Little-Endian的。
同樣的類比,UTF-32也是這樣的。有UTF-32BE、UTF-32LE、UTF-32。前面UTF-32BE和UTF-32LE直接指定了字節(jié)序(大小端),后面的UTF-32也是靠BOM。
UTF-8不需要BOM來表明字節(jié)順序,但可以用BOM來表明編碼方式。字符"ZERO WIDTH NO-BREAKSPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗(yàn)證一下)。所以如果接收者收到以EF BB BF開頭的字節(jié)流,就知道這是UTF-8編碼了。
Windows就是使用BOM來標(biāo)記文本文件的編碼方式的。它就建議所有的 Unicode 文件應(yīng)該以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字符開頭。這作為一個(gè)“特征符”或“字節(jié)順序標(biāo)記(byte-ordermark,BOM)”來識別文件中使用的編碼和字節(jié)順序。所以用Windows自帶的記事本將文件保存為UTF-8編碼的時(shí)候,記事本會自動在文件開頭插入BOM(雖然BOM對UTF-8來說并不是必須的)。
但也有一些系統(tǒng)或程序不支持BOM,因此帶有BOM的Unicode文件有時(shí)會帶來一些問題。比如JDK1.5以及之前的Reader都不能處理帶有BOM的UTF-8編碼的文件,解析這種格式的xml文件時(shí),會拋出異常:Content is not allowed inprolog。
Linux/UNIX 并沒有使用 BOM,因?yàn)樗鼤茐默F(xiàn)有的 ASCII 文件的語法約定。所以一般我們不建議用Windows自帶的記事本編輯UTF-8文件就是這樣。
四
總結(jié)
1、簡單地說:Unicode和UCS是字符集,不屬于編碼UTF-8、UTF-16、UTF-32等是針對Unicode字符集的編碼,UCS-2和UCS-4是針對UCS字符集的編碼(只是我們習(xí)慣把Unicode字符集編碼簡稱為Unicode編碼,把UCS字符集編碼稱為UCS編碼)。
Unicode沿用UCS字符集,在UCS-2和UCS-4基礎(chǔ)上提出的UTF-16、UTF-32。并發(fā)展了UTF-8,發(fā)展到現(xiàn)在,就密不可分了,大家基于UCS就把Uniocde維護(hù)好就行,發(fā)布標(biāo)準(zhǔn)大家統(tǒng)一。以往的UCS-2和UCS-4概念就默認(rèn)作廢了這樣一個(gè)關(guān)系,整個(gè)他們的發(fā)展長話短說就是這樣,懂了嗎。
2、UTF-8、UTF-16、UTF-32、UCS-2、UCS-4對比:
由于歷史方面的原因,你還會在不少地方看到把Unicode稱為一種編碼的情況,那是因?yàn)?span style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-weight: 700; margin: 0px; padding: 0px; border: 0px;">早期的2字節(jié)編碼最初稱為“ Unicode”,但現(xiàn)在稱為“ UCS-2”,這種情況下的 Unicode 通常就是 UTF-16 或者是更早的 UCS-2 編碼,只是被一直搞混了,在某些老軟件上尤為常見。比如下面editplus里面文件編碼設(shè)置。
以前的Windows電腦上的記事本(左邊)顯示的是Unicode,不過現(xiàn)在好像改了變成了UTF-16。
不過由于各種原因,必須承認(rèn),在不同的語境下,“Unicode”這個(gè)詞有著不同的含義。
它可能指:
(1)Unicode 標(biāo)準(zhǔn)
(2)Unicode 字符集
(3)Unicode 的抽象編碼(編號),也即碼點(diǎn)、碼位(code point)
(4)Unicode 的一個(gè)具體編碼實(shí)現(xiàn),通常即為變長的 UTF-16(16 或 32 位),又或者是更早期的定長 16 位的 UCS-2
所以像我一般有時(shí)候非要區(qū)分的話都是直接說全,Unicode 標(biāo)準(zhǔn),Unicode 字符集,Unicode編碼等等。
五
ANSI編碼
為使計(jì)算機(jī)支持更多語言,通常使用0x800~xFF范圍的2個(gè)字節(jié)來表示1個(gè)字符。比如:漢字‘中’ 在中文操作系統(tǒng)中,使用 [0xD6,0xD0]這兩個(gè)字節(jié)存儲。
不同的國家和地區(qū)制定了不同的標(biāo)準(zhǔn),由此產(chǎn)生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的編碼標(biāo)準(zhǔn)。這些使用多個(gè)字節(jié)來代表一個(gè)字符的各種語言延伸編碼方式,稱為 ANSI 編碼。
在簡體中文Windows操作系統(tǒng)中,ANSI 編碼代表 GBK 編碼;在繁體中文Windows操作系統(tǒng)中,ANSI編碼代表Big5;在日文Windows操作系統(tǒng)中,ANSI 編碼代表 Shift_JIS 編碼。
不同 ANSI 編碼之間互不兼容,當(dāng)信息在國際間交流時(shí),無法將屬于兩種語言的文字,存儲在同一段 ANSI 編碼的文本中。
在使用ANSI編碼支持多語言階段,每個(gè)字符使用一個(gè)字節(jié)或多個(gè)字節(jié)來表示(MBCS,Multi-Byte Character System),因此,這種方式存放的字符也被稱作多字節(jié)字符。比如,“中文123” 在中文 Windows 95 內(nèi)存中為7個(gè)字節(jié),每個(gè)漢字占2個(gè)字節(jié),每個(gè)英文和數(shù)字字符占1個(gè)字節(jié)。
在非 Unicode 環(huán)境下,由于不同國家和地區(qū)采用的字符集不一致,很可能出現(xiàn)無法正常顯示所有字符的情況。微軟公司使用了代碼頁(Codepage)轉(zhuǎn)換表的技術(shù)來過渡性的部分解決這一問題,即通過指定的轉(zhuǎn)換表將非Unicode 的字符編碼轉(zhuǎn)換為同一字符對應(yīng)的系統(tǒng)內(nèi)部使用的Unicode 編碼。
可以在“語言與區(qū)域設(shè)置”中選擇一個(gè)代碼頁作為非 Unicode 編碼所采用的默認(rèn)編碼方式,如936為簡體中文GBK,950為正體中文Big5(皆指PC上使用的)。在這種情況下,一些非英語的歐洲語言編寫的軟件和文檔很可能出現(xiàn)亂碼。而將代碼頁設(shè)置為相應(yīng)語言中文處理又會出現(xiàn)問題,這一情況無法避免。
從根本上說,完全采用統(tǒng)一編碼才是解決之道,雖然現(xiàn)在Unicode有了,但由于歷史遺留,老軟件等等原因,所以系統(tǒng)統(tǒng)一用某種編碼格式的Unicode目前尚無法做到這一點(diǎn)。
代碼頁技術(shù)現(xiàn)在廣泛為各種平臺所采用。UTF-7 的代碼頁是65000,UTF-8的代碼頁是65001。簡體中文上使用的代碼頁為936,GBK編碼。
以前中文DOS、中文/日文Windows95/98時(shí)代系統(tǒng)內(nèi)碼使用的是ANSI編碼(本地化,根據(jù)不同地區(qū)設(shè)置不同的系統(tǒng)內(nèi)碼Windows版本),現(xiàn)在win7,win10等等系統(tǒng)的內(nèi)碼都是用的Unicode。
不過微軟為了以前的程序兼容性,比如在某些情況下,比如你的程序需要和不支持Unicode的程序交互時(shí),可能還是會需要用到code page,提供代碼頁服務(wù)(就好比微軟不能說:“老子支持unicode了,以后不支持Unicode的程序都給我滾粗。”只能撅著屁股讓這些老掉牙的程序仍然可以運(yùn)行,于是只好給他們提供一個(gè)“非Unicode默認(rèn)字符集”) 。可以在cmd下輸入chcp查看code page。
WindowsAPI 的Wide Char 表達(dá)是 UTF-16: Unicode (Windows), L"" 表示是轉(zhuǎn)換為 wide char。
Cocoa的NSString 和 Core Foundation 的CFString 內(nèi)部表達(dá)都是 UTF-16,所以其實(shí) OSX 和 iOS 內(nèi)部處理都用的是 UTF-16。
JavaString 的內(nèi)部表達(dá)是 UTF-16,所以大量跨平臺程序和 Android 程序其實(shí)內(nèi)部也在用 UTF-16。
大部分的操作系統(tǒng)和 UI framework 的內(nèi)部字符串表達(dá)(內(nèi)碼)都是UTF-16,不過Linux系統(tǒng)內(nèi)使用的內(nèi)碼是UTF-8。
六
Tip:內(nèi)碼和外碼
在計(jì)算機(jī)科學(xué)及相關(guān)領(lǐng)域當(dāng)中,內(nèi)碼指的是“將信息編碼后,透過某種方式存儲在特定記憶設(shè)備時(shí),設(shè)備內(nèi)部的編碼形式”。在不同的系統(tǒng)中,會有不同的內(nèi)碼。
在以往的英文系統(tǒng)中,內(nèi)碼為ASCII。在繁體中文系統(tǒng)中,當(dāng)前常用的內(nèi)碼為大五碼。在簡體中文系統(tǒng)中,內(nèi)碼則為國標(biāo)碼。
為了軟件開發(fā)方便,如國際化與本地化,現(xiàn)在許多系統(tǒng)會使用Unicode做為內(nèi)碼,常見的操作系統(tǒng)Windows、Mac OS X、Linux皆如此。許多編程語言也采用Unicode為內(nèi)碼,如Java、Python3。
外碼:除了內(nèi)碼,皆是外碼。要注意的是,源代碼編譯產(chǎn)生的目標(biāo)代碼文件(如果Java可執(zhí)行文件或class文件)中的編碼方式屬于外碼。