【转】刨根究底字符编码之十六——Windows记事本的诡异怪事:微软为什么跟联通有仇?
1.
當用一個軟件(比如Windows記事本或Notepad++)打開一個文本文件時,它要做的第一件事是確定這個文本文件究竟是使用哪種編碼方式保存的,以便于該軟件對其正確解碼,否則將顯示為亂碼。
一般軟件確定文本文件編碼方式的方法有如下三種:
- 檢測文件頭標識;
- 提示用戶手動選擇;
- 根據(jù)一定的規(guī)則自行推斷。
2.
文件頭標識一般指的是字節(jié)順序標記BOM(Byte Order Mark),位于文件的最開始。當打開一個文本文件時,就BOM而言,有如下幾種情形:
- BOM為:EF BB BF ——表示編碼方式為UTF-8;
- BOM為:FF FE ——表示編碼方式為UTF-16LE(小端序);
- BOM為:FE FF ——表示編碼方式為UTF-16BE(大端序);
- BOM為:FF FE 00 00 ——表示編碼方式為UTF-32LE(小端序);
- BOM為:00 00 FE FF ——表示編碼方式為UTF-32BE(大端序);
- 沒有BOM ——要么顯式地提示用戶手動選擇一種編碼方式,要么隱式地由軟件按規(guī)則自行推斷出編碼方式。
3.
接下來,是見證詭異怪事的時刻。
當你在簡體中文版的Windows記事本里新建一個文件,輸入“聯(lián)通”兩個漢字之后,保存為一個txt文件。然后關(guān)閉,再次打開該txt文件后,你會發(fā)現(xiàn)剛才輸入并保存的“聯(lián)通”兩個漢字竟然莫名其妙地消失了,取而代之的是幾個亂碼。如下圖所示。
這是為什么呢?難道是微軟跟聯(lián)通有仇嗎?
原來,當你用Windows記事本新建一個文本文件時,其編碼方式默認為ANSI編碼(在簡體中文版Windows中實際為GBK編碼),沒有BOM。
(注:Windows系統(tǒng)中的ANSI編碼指的是在區(qū)域設(shè)置中所設(shè)置的系統(tǒng)默認編碼方式,在簡體中文版Windows系統(tǒng)中指的是GBK,即CP936代碼頁,具體可參看前文《刨根究底字符編碼之七——ANSI編碼與代碼頁》)
(笨笨阿林原創(chuàng)文章,轉(zhuǎn)載請注明出處)
在這種編碼方式下,該文本文件僅僅保存了“聯(lián)通”兩個漢字的GB內(nèi)碼的四個字節(jié),如下所示(左邊為十六進制,右邊為二進制)。
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
通過Notepad++的HEX-Editor插件可查看內(nèi)碼(十六進制),如下圖所示。
通過UltraEdit的“十六進制編輯”模式也可查看內(nèi)碼(十六進制),如下圖所示。
4.
當用記事本再次打開該文本文件時,由于沒有BOM,記事本又沒有提供顯式地提示用戶手動選擇編碼方式的功能,于是就只能隱式地按其推斷規(guī)則自行推斷,推斷的結(jié)果就是被誤認為了這是一個UTF-8編碼方式的文件。
為什么會推斷錯誤呢?又為什么會將其編碼方式錯誤地推斷為UTF-8呢?
注意,“聯(lián)通”兩個漢字的GB內(nèi)碼,其第一第二個字節(jié)的起始部分分別是“110”和“10”,第三第四個字節(jié)的起始部分也分別是“110”和“10”,這剛好符合了UTF-8編碼方式里的兩碼元序列的編碼算法規(guī)則(即與UTF-8的兩碼元序列“110xxxxx 10xxxxxx”中的前綴碼“110”和“10”剛好是完全一致的;詳見本系列文章中《刨根究底字符編碼之十二——UTF-8究竟是怎么編碼的》一文的介紹)。
讓我們按照UTF-8的編碼算法規(guī)則,將第一個字節(jié)的前綴碼110去掉,得到“00001”,將第二個字節(jié)的前綴碼10去掉,得到“101010”,將兩者組合在一起,得到“00001101010”,再去掉多余的前導的0,就得到了“0110 1010"(十六進制為6A),這正好是Unicode字符集里的U+006A,也就是小寫字母“j”的碼點值。
同理,之后的第三個字節(jié)與第四個字節(jié)按同樣的方法用UTF-8解碼之后正好是Unicode字符集里的U+0368,這個字符為“?”(抱歉,這里的左雙引號貌似被這個字符所影響,看起來像是半角左雙引號,而無法正常顯示為全角左雙引號),很像是上標的一個小c,這應(yīng)該是個組合字符(組合字符是Unicode字符集中的一種特殊字符,必須與其他字符組合在一起以形成一個新字符,一般不單獨使用,可參看本系列文章前面相關(guān)文章中的介紹)。
這就是只有“聯(lián)通”兩個漢字的文本文件沒有辦法在記事本里被正確解碼顯示的原因。這里要特別說明的是,在記事本里打開時顯示的不是“j”和“?”,而是顯示為了“��?”(注意右上角是“?”)。
而用UltraEdit打開,如果在設(shè)置中選擇了“自動檢測UTF-8文件”,顯示的是“j”和“?”組合在一起的字符“j?”。注意這個字符不是小寫字母“j”,而是小寫字母“j”上面的點變成了一個上標的小c,因為U+0368這個字符“?”應(yīng)該是個組合字符,與其前面的小寫字母“j”組合在一起而形成了一個新字符——j?(再次提醒注意:小寫字母“j”上面的點變成了“c”)。
(注意:在UltraEdit的早期版本中,沒有“自動檢測UTF-8文件”這一選項)
5.
這里還有一個問題:既然已經(jīng)推斷為了UTF-8,那為什么Windows記事本還是將前兩個字節(jié),亦即原本為“聯(lián)”字的GB內(nèi)碼的那兩個字節(jié),顯示為了“��”這樣的亂碼,而不是顯示為小寫字母“j”呢?
我想主要是因為小寫字母“j”屬于ASCII字符,在UTF-8編碼中ASCII字符屬于單字節(jié)編碼,出現(xiàn)在雙字節(jié)編碼中是非正常的,因而被Windows記事本認為是錯誤編碼,而UltraEdit則作了容錯處理,仍然將其解讀為了小寫字母“j”。
而后兩個字節(jié),亦即原本為“通”字的GB內(nèi)碼的那兩個字節(jié),之所以Windows記事本將其按UTF-8編碼的規(guī)則解讀為了字符“?”,那是因為字符“?”的UTF-8編碼正好就是雙字節(jié)編碼,因此按UTF-8編碼的規(guī)則去解讀的話不屬于錯誤。
(笨笨阿林原創(chuàng)文章,轉(zhuǎn)載請注明出處)
6.
其實,用記事本默認的編碼方式(ANSI)分別單獨保存“聯(lián)”字和“通”字為兩個獨立的txt文件,則:
1) 再用記事本打開時,“聯(lián)”字顯示的是“��”,“通”字顯示的是“?”;
2) 用UltraEdit打開時,
(1) 如果選擇了“自動檢測UTF-8文件”,“聯(lián)”字顯示的是小寫字母“j”,“通”字顯示的“?”(不過看不清,我開始還以為是個空格);
(2) 如果沒有選擇“自動檢測UTF-8文件”,“聯(lián)”字和“通”字均能正常顯示(說明這種情況下UltraEdit正確地推斷出了編碼方式為GBK,從這一點來看,UltraEdit比Windows記事本要強);
3) 用NotePad++打開時,
(1) 如果在“格式”中選擇的是“以ANSI格式編碼”(亦即顯式地手動選擇了正確的編碼方式),“聯(lián)”字和“通”字均能正常顯示;
(2) 如果編碼方式選擇的是UTF-8、UTF-8無BOM、UCS-2 Big Endian或UCS-2 Little Endian時,則“聯(lián)”字均顯示為“xC1xAA”(有意思的是,直接復(fù)制“xC1xAA”然后粘貼到Word里,則顯示為了小寫字母“j”),“通”字均顯示為“?”。
而如果是用記事本默認的編碼方式(ANSI)保存“聯(lián)通通信”四個字,則用記事本、UltraEdit(即便選擇的是“自動檢測UTF-8文件”的情況下)打開后都可正常顯示。
這充分說明,Windows記事本在文件頭沒有BOM的情況下,只能自行推斷,由于“聯(lián)通”兩個漢字保存為ANSI編碼方式時,內(nèi)碼只有四個字節(jié),在信息不夠充足的情況下(尤其是其內(nèi)碼又剛好符合了UTF-8的編碼算法規(guī)則),于是被錯誤地推斷為了UTF-8編碼方式;當以ANSI編碼方式保存的是“聯(lián)通通信”四個漢字時,內(nèi)碼有八個字節(jié),這時信息較為充足,因此被正確地推斷為了ANSI編碼方式(在簡體中文版Windows中ANSI編碼默認為GBK編碼)。
7.
上面分析的是Windows系統(tǒng)中采用ANSI編碼時沒有添加BOM的情況。那么,對于采用非ANSI編碼時添加了BOM的情況,是否就萬事大吉了呢?其實,添加BOM來標記字符編碼表面看起來貌似不錯,但實際上經(jīng)常會帶來麻煩,因為它和很多協(xié)議、規(guī)范并不兼容。
Windows里的軟件在采用非ANSI編碼時,即便對于根本不存在字節(jié)序問題的UTF-8編碼默認也會添加BOM(詳見之前文章《刨根究底字符編碼之十一——UTF-8編碼方式與字節(jié)序標記》的介紹),而像Unix、Linux、Mac OS等*nix系統(tǒng)對于UTF-8編碼都默認不添加BOM。
既然*nix系統(tǒng)都可以不添加BOM,那為什么Windows系統(tǒng)卻非要添加BOM呢?這很可能是因為Windows系統(tǒng)有大量普通用戶使用,在必須兼容傳統(tǒng)ANSI編碼的情況下,從用戶體驗角度考慮而沒有采用顯式地要求用戶手動選擇字符編碼方式的做法,因此特別依賴于通過BOM來防止隱式地自行推斷字符編碼方式而出錯。
微軟這種為了照顧廣大普通用戶而從用戶體驗角度出發(fā)“好心辦壞事”的例子其實還有很多。
8.
因此,在Windows系統(tǒng)中,盡量不要使用記事本來打開并編輯文本文件,尤其是作為程序員,應(yīng)使用Notepad++或UltraEdit等更為專業(yè)的文本文件編輯軟件。
這一方面是可以避免出現(xiàn)上述這樣的“詭異”錯誤,另一方面也是為了避免Windows記事本“多此一舉”地添加BOM(詳見下面附文中的解釋),從而給在與其他系統(tǒng)(比如*nix系統(tǒng))交流時帶來不必要的麻煩。
?
附:Windows記事本中對常用編碼方式自行其是的“奇葩”命名
Windows記事本中,對常用編碼方式的命名非?!捌孑狻?#xff0c;微軟這種自行其是的非標準命名,很是令人費解,現(xiàn)解釋如下。
1) ANSI指的是對應(yīng)當前系統(tǒng)區(qū)域設(shè)置(即系統(tǒng)locale)中的默認ANSI編碼,不帶BOM。在簡體中文版Windows系統(tǒng)中默認ANSI編碼指的就是GBK編碼,即CP936,具體可參看前文《刨根究底字符編碼之七——ANSI編碼與代碼頁》。
2) Unicode指的是帶有BOM的小端序UTF-16(即UTF-16LE with BOM)。
3) Unicode big endian指的是帶有BOM的大端序UTF-16(即UTF-16BE with BOM)。
4) UTF-8指的是帶有BOM的UTF-8(即UTF-8 with BOM)。UTF-8編碼方式實際上并不存在字節(jié)序的問題,之所以仍然“多此一舉”地添加BOM,應(yīng)該是由于要兼容不添加BOM的ANSI編碼,從用戶體驗角度考慮,避免用戶顯式地手動選擇編碼方式。
(注:如果UTF-8編碼不添加BOM,則有兩種不添加BOM的編碼方式,從而導致隱式地自行推斷編碼方式更容易出錯,上文所介紹的對“聯(lián)通”推斷出錯即是明證。當然反過來也說明了Windows記事本對于不添加BOM的UTF-8編碼其實同樣是支持的,而并非簡單粗暴地直接提示錯誤,這應(yīng)該是為了兼容*nix系統(tǒng)不添加BOM的做法而不得不采取的策略。只是這樣一來,就很難避免陷入左右為難的困境。)
(笨笨阿林原創(chuàng)文章,轉(zhuǎn)載請注明出處)
?
(未完待續(xù))
總結(jié)
以上是生活随笔為你收集整理的【转】刨根究底字符编码之十六——Windows记事本的诡异怪事:微软为什么跟联通有仇?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 结转成本是什么意思
- 下一篇: 总价1500元!小米商城上线北京绿色节能