刨根究底字符編碼之十六——Windows記事本的詭異怪事:微軟爲何跟聯通有仇?(沒有BOM,因此被誤判爲UTF8。「聯通」兩個漢字的GB內碼,其第一第二個字節的起始部分分別是「110」和「10」,,

1.程序員

當用一個軟件(好比Windows記事本或Notepad++)打開一個文本文件時,它要作的第一件事是肯定這個文本文件到底是使用哪一種編碼方式保存的,以便於該軟件對其正確解碼,不然將顯示爲亂碼。算法

通常軟件肯定文本文件編碼方式的方法有以下三種:編碼

  • 檢測文件頭標識;
  • 提示用戶手動選擇;
  • 根據必定的規則自行推斷。

2.插件

文件頭標識通常指的是字節順序標記BOM(Byte Order Mark),位於文件的最開始。當打開一個文本文件時,就BOM而言,有以下幾種情形:3d

  • 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 ——要麼顯式地提示用戶手動選擇一種編碼方式,要麼隱式地由軟件按規則自行推斷出編碼方式。

3.code

接下來,是見證詭異怪事的時刻。orm

當你在簡體中文版的Windows記事本里新建一個文件,輸入「聯通」兩個漢字以後,保存爲一個txt文件。而後關閉,再次打開該txt文件後,你會發現剛纔輸入並保存的「聯通」兩個漢字居然莫名其妙地消失了,取而代之的是幾個亂碼。以下圖所示。blog

這是爲何呢?難道是微軟跟聯通有仇嗎?get

原來,當你用Windows記事本新建一個文本文件時,其編碼方式默認爲ANSI編碼(在簡體中文版Windows中實際爲GBK編碼),沒有BOM。it

(注:Windows系統中的ANSI編碼指的是在區域設置中所設置的系統默認編碼方式,在簡體中文版Windows系統中指的是GBK,即CP936代碼頁,具體可參看前文《刨根究底字符編碼之七——ANSI編碼與代碼頁》)

(笨笨阿林原創文章,轉載請註明出處)

在這種編碼方式下,該文本文件僅僅保存了「聯通」兩個漢字的GB內碼的四個字節,以下所示(左邊爲十六進制,右邊爲二進制)。

  c1  1100 0001
  aa  1010 1010
  cd  1100 1101
  a8  1010 1000

經過Notepad++的HEX-Editor插件可查看內碼(十六進制),以下圖所示。

經過UltraEdit的「十六進制編輯」模式也可查看內碼(十六進制),以下圖所示。

4.

當用記事本再次打開該文本文件時,因爲沒有BOM,記事本又沒有提供顯式地提示用戶手動選擇編碼方式的功能,因而就只能隱式地按其推斷規則自行推斷,推斷的結果就是被誤認爲了這是一個UTF-8編碼方式的文件。

爲何會推斷錯誤呢?又爲何會將其編碼方式錯誤地推斷爲UTF-8呢?

注意,「聯通」兩個漢字的GB內碼,其第一第二個字節的起始部分分別是「110」和「10」,第三第四個字節的起始部分也分別是「110」和「10」,這恰好符合了UTF-8編碼方式裏的兩碼元序列的編碼算法規則(即與UTF-8的兩碼元序列「110xxxxx 10xxxxxx」中的前綴碼「110」和「10」恰好是徹底一致的;詳見本系列文章中《刨根究底字符編碼之十二——UTF-8到底是怎麼編碼的》一文的介紹)。

讓咱們按照UTF-8的編碼算法規則,將第一個字節的前綴碼110去掉,獲得「00001」,將第二個字節的前綴碼10去掉,獲得「101010」,將二者組合在一塊兒,獲得「00001101010」,再去掉多餘的前導的0,就獲得了「0110 1010",這正好是Unicode字符集裏的U+006A,也就是小寫字母「j」的碼點值。

同理,以後的第三個字節與第四個字節按一樣的方法用UTF-8解碼以後正好是Unicode字符集裏的U+0368,這個字符爲「ͨ」(抱歉,這裏的左雙引號貌似被這個字符所影響,看起來像是半角左雙引號,而沒法正常顯示爲全角左雙引號),很像是上標的一個小c,這應該是個組合字符(組合字符是Unicode字符集中的一種特殊字符,必須與其餘字符組合在一塊兒以造成一個新字符,通常不單獨使用,可參看本系列文章前面相關文章中的介紹)。

這就是隻有「聯通」兩個漢字的文本文件沒有辦法在記事本里被正確解碼顯示的緣由。這裏要特別說明的是,在記事本里打開時顯示的不是「j」和「ͨ」,而是顯示爲了「��ͨ」(注意右上角是「ͨ」)。

而用UltraEdit打開,若是在設置中選擇了「自動檢測UTF-8文件」,顯示的是「j」和「ͨ」組合在一塊兒的字符「jͨ」。注意這個字符不是小寫字母「j」,而是小寫字母「j」上面的點變成了一個上標的小c,由於U+0368這個字符「ͨ」應該是個組合字符,與其前面的小寫字母「j」組合在一塊兒而造成了一個新字符——jͨ(再次提醒注意小寫字母「j」上面的點變成了「c」)。

(注意:在UltraEdit的早期版本中,沒有「自動檢測UTF-8文件」這一選項)

5.

這裏還有一個問題:既然已經推斷爲了UTF-8,那爲何Windows記事本仍是將前兩個字節,亦即本來爲「聯」字的GB內碼的那兩個字節,顯示爲了「��」這樣的亂碼,而不是顯示爲小寫字母「j」呢?

我想主要是由於小寫字母「j」屬於ASCII字符,在UTF-8編碼中ASCII字符屬於單字節編碼,出如今雙字節編碼中是非正常的,於是被Windows記事本認爲是錯誤編碼,而UltraEdit則做了容錯處理,仍然將其解讀爲了小寫字母「j」。

然後兩個字節,亦即本來爲「通」字的GB內碼的那兩個字節,之因此Windows記事本將其按UTF-8編碼的規則解讀爲了字符「ͨ」,那是由於字符「ͨ」的UTF-8編碼正好就是雙字節編碼,所以按UTF-8編碼的規則去解讀的話不屬於錯誤。

(笨笨阿林原創文章,轉載請註明出處)

6.

其實,用記事本默認的編碼方式(ANSI)分別單獨保存「聯」字和「通」字爲兩個獨立的txt文件,則:

1) 再用記事本打開時,「聯」字顯示的是「��」,「通」字顯示的是「ͨ」;

2) 用UltraEdit打開時,

  (1) 若是選擇了「自動檢測UTF-8文件」,「聯」字顯示的是小寫字母「j」,「通」字顯示的「ͨ」(不過看不清,我開始還覺得是個空格);

  (2) 若是沒有選擇「自動檢測UTF-8文件」,「聯」字和「通」字均能正常顯示(說明這種狀況下UltraEdit正確地推斷出了編碼方式爲GBK,從這一點來看,UltraEdit比Windows記事本要強);

3) 用NotePad++打開時,

  (1) 若是在「格式」中選擇的是「以ANSI格式編碼」(亦即顯式地手動選擇了正確的編碼方式),「聯」字和「通」字均能正常顯示;

  (2) 若是編碼方式選擇的是UTF-八、UTF-8無BOM、UCS-2 Big Endian或UCS-2 Little Endian時,則「聯」字均顯示爲「xC1xAA」(有意思的是,直接複製「xC1xAA」而後粘貼到Word裏,則顯示爲了小寫字母「j」),「通」字均顯示爲「ͨ」。

而若是是用記事本默認的編碼方式(ANSI)保存「聯統統信」四個字,則用記事本、UltraEdit(即使選擇的是「自動檢測UTF-8文件」的狀況下)打開後均可正常顯示。

這充分說明,Windows記事本在文件頭沒有BOM的狀況下,只能自行推斷,因爲「聯通」兩個漢字保存爲ANSI編碼方式時,內碼只有四個字節,在信息不夠充足的狀況下(尤爲是其內碼又恰好符合了UTF-8的編碼算法規則),因而被錯誤地推斷爲了UTF-8編碼方式;當以ANSI編碼方式保存的是「聯統統信」四個漢字時,內碼有八個字節,這時信息較爲充足,所以被正確地推斷爲了ANSI編碼方式(在簡體中文版Windows中ANSI編碼默認爲GBK編碼)。

7.

上面分析的是Windows系統中採用ANSI編碼時沒有添加BOM的狀況。那麼,對於採用非ANSI編碼時添加了BOM的狀況,是否就萬事大吉了呢?其實,添加BOM來標記字符編碼表面看起來貌似不錯,但實際上常常會帶來麻煩,由於它和不少協議、規範並不兼容。

Windows裏的軟件在採用非ANSI編碼時,即使對於根本不存在字節序問題的UTF-8編碼默認也會添加BOM(詳見以前文章《刨根究底字符編碼之十一——UTF-8編碼方式與字節序標記》的介紹),而像Unix、Linux、Mac OS等*nix系統對於UTF-8編碼都默認不添加BOM。

既然*nix系統均可以不添加BOM,那爲何Windows系統卻非要添加BOM呢?這極可能是由於Windows系統有大量普通用戶使用,在必須兼容傳統ANSI編碼的狀況下,從用戶體驗角度考慮而沒有采用顯式地要求用戶手動選擇字符編碼方式的作法,所以特別依賴於經過BOM來防止隱式地自行推斷字符編碼方式而出錯。

微軟這種爲了照顧廣大普通用戶而從用戶體驗角度出發「好心辦壞事」的例子其實還有不少。

8.

所以,在Windows系統中,儘可能不要使用記事原本打開並編輯文本文件,尤爲是做爲程序員,應使用Notepad++或UltraEdit等更爲專業的文本文件編輯軟件。

這一方面是能夠避免出現上述這樣的「詭異」錯誤,另外一方面也是爲了不Windows記事本「畫蛇添足」地添加BOM(詳見下面附文中的解釋),從而給在與其餘系統(好比*nix系統)交流時帶來沒必要要的麻煩。

 

附:Windows記事本中對經常使用編碼方式自行其是的「奇葩」命名

Windows記事本中,對經常使用編碼方式的命名很是「奇葩」,微軟這種自行其是的非標準命名,乍一看使人費解,現解釋以下。

  1) ANSI指的是對應當前系統區域設置(即系統locale)中的默認ANSI編碼,不帶BOM。在簡體中文版Windows系統中默認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編碼方式實際上並不存在字節序的問題,之因此仍然「畫蛇添足」地添加BOM,應該是因爲要兼容不添加BOM的ANSI編碼,從用戶體驗角度考慮,避免用戶顯式地手動選擇編碼方式。

  (注:若是UTF-8編碼不添加BOM,則有兩種不添加BOM的編碼方式,從而致使隱式地自行推斷編碼方式更容易出錯,上文所介紹的對「聯通」推斷出錯便是明證。固然反過來也說明了Windows記事本對於不添加BOM的UTF-8編碼其實一樣是支持的,而並不是簡單粗暴地直接提示錯誤,這應該是爲了兼容*nix系統不添加BOM的作法而不得不採起的策略。只是這樣一來,就很難避免陷入左右爲難的困境。)

(笨笨阿林原創文章,轉載請註明出處)

 

(未完待續)

 

https://zhuanlan.zhihu.com/p/86871840

相關文章
相關標籤/搜索