小村軟體有限公司
第 1 章

導入 — 亂碼為什麼會發生

檔案的實體只有位元組序列。「寫入的編碼」與「讀取的編碼」一旦錯位,同一串位元組看起來就是別的字元 — 在這一章先掌握亂碼的真面目。

「縺薙s縺ォ縺。縺ッ」— 這個畫面上發生了什麼事

打開客戶寄來的 CSV,日文全變成「縺薙s縺ォ縺。縺ッ」這種漢字的羅列。網頁的一部分排滿「ã“ã‚“」之類的符號。明明已經登錄進資料庫的名字變成「????」。— 就算有過「換個編碼重新開啟就好了」的處置經驗,能說明為什麼這樣就會好的人卻意外地少。

出發點只有一個事實:電腦能儲存、能傳送的,只有 0x00〜0xFF 的位元組排列。日文字元「あ」或「A」這些字元本身並沒有被儲存下來。把字元轉換成位元組序列的規則稱為編碼(encoding),UTF-8 與 Shift_JIS 都是這種規則的一種。

而重要的是下面這一點:檔案上並沒有掛著「這串位元組是 UTF-8」的標籤。讀取方只能依靠設定、宣告或猜測,認定「大概是這種編碼吧」然後去查對應表。寫入方的規則與讀取方的規則一致,就能還原成原本的字元;一旦錯位 — 看起來就是別的字元。這就是亂碼。

顯示位元組序列 E3 81 AB 以 UTF-8 讀取是「に」、以 Shift_JIS 讀取是「縺ォ」、以 Latin-1 讀取是「ã«」的圖

同樣的 3 個位元組,依查的對應表不同,會變成「に」「縺ォ」「ã«」。位元組序列連 1 個位元都沒有改變 — 這是最大的重點。

就算亂掉,資料也沒有壞

請再看一次上面的圖。E3 81 AB 這 3 個位元組,不管用哪種讀法,都維持完全相同的 3 個位元組。壞掉的不是資料,只是「讀法的選擇」。所以只要指定正確的編碼重新讀取,原本的「に」就會原封不動地回來。

不過有一件事必須注意。若以亂掉的樣子直接覆寫存檔,「縺ォ」這串亂掉的字元就會作為新的位元組序列寫入磁碟,原本的位元組序列就此消失。遇到亂碼時「先不要寫入,先懷疑讀法」— 這是實務上的第一個動作。

本課程的口訣:看到亂碼時請問自己 — 這是用哪種編碼寫入的位元組序列,被當成了哪種編碼來讀取?

小題 1-1 — 檔案的實體只有位元組序列

確認你能不能把亂碼的真面目,說成位元組序列與讀法之間的關係。

Q1. 以文字檔形式儲存在磁碟上的「實體」是什麼?

Q2. 同一串位元組 E3 81 AB,當作 UTF-8 讀是日文字元「に」,當作 Shift_JIS 讀則看起來是「縺ォ」。對這個現象的正確說明是哪一個?

Q3. 關於出現亂碼的檔案,多數情況下正確的說明是哪一個?

「編上號碼」與「變成位元組」— 分成 2 個階段思考

在這裡,我們引入貫穿整個課程的觀點。字元變成位元組序列之前,其實存在 2 個彼此獨立的階段。

第 1 階段 — 字元集(哪個字元編上哪個號碼)
把世界上的字元收集起來,逐一分配號碼的表。在 Unicode 中,日文字元「あ」被編上 U+3042 這個號碼。這個號碼稱為碼位(code point)
第 2 階段 — 編碼(如何把號碼變成位元組)
把碼位轉換成位元組序列的規則。同樣是 U+3042,UTF-8 的結果是 E3 81 82,UTF-16 則是 30 42

「Unicode 和 UTF-8 到底差在哪?」這個經典疑問,用這 2 個階段就能立刻回答:Unicode 是第 1 階段(號碼的編法),UTF-8 是第 2 階段(號碼變成位元組的方法)。可以比喻成「決定門牌號碼的方式」與「把門牌號碼寫上信封的方式」的差別。第 2 章會仔細處理第 1 階段,第 3 章之後則仔細處理第 2 階段。

小題 1-2 — 區分字元集與編碼

確認貫穿本課程的 2 個階段 — 編上號碼(字元集)與變成位元組(編碼)— 的區別。

Q4. 「字元集」與「編碼」的分工,正確的是哪一個?

Q5. 「這個檔案是以 Unicode 儲存的」這種說法之所以不精確,原因是哪一個?

本章帶走的重點

  • 檔案的實體只有位元組序列,上面沒有掛「是什麼編碼」的標籤
  • 亂碼的真面目是「寫入的編碼」與「讀取的編碼」的錯位。位元組序列本身多半沒有壞,修正讀法就能還原(但不要在亂掉的狀態下存檔)
  • 字元以號碼(碼位)→ 位元組序列(編碼)的 2 個階段來處理。Unicode 是第 1 階段,UTF-8 是第 2 階段

下一章將回顧第 1 階段「編上號碼」的歷史,從 ASCII 的 128 個字元,一路走到收納世界上所有字元的 Unicode。