導入 — 亂碼為什麼會發生
檔案的實體只有位元組序列。「寫入的編碼」與「讀取的編碼」一旦錯位,同一串位元組看起來就是別的字元 — 在這一章先掌握亂碼的真面目。
「縺薙s縺ォ縺。縺ッ」— 這個畫面上發生了什麼事
打開客戶寄來的 CSV,日文全變成「縺薙s縺ォ縺。縺ッ」這種漢字的羅列。網頁的一部分排滿「ã“ã‚“」之類的符號。明明已經登錄進資料庫的名字變成「????」。— 就算有過「換個編碼重新開啟就好了」的處置經驗,能說明為什麼這樣就會好的人卻意外地少。
出發點只有一個事實:電腦能儲存、能傳送的,只有 0x00〜0xFF 的位元組排列。日文字元「あ」或「A」這些字元本身並沒有被儲存下來。把字元轉換成位元組序列的規則稱為編碼(encoding),UTF-8 與 Shift_JIS 都是這種規則的一種。
而重要的是下面這一點:檔案上並沒有掛著「這串位元組是 UTF-8」的標籤。讀取方只能依靠設定、宣告或猜測,認定「大概是這種編碼吧」然後去查對應表。寫入方的規則與讀取方的規則一致,就能還原成原本的字元;一旦錯位 — 看起來就是別的字元。這就是亂碼。
同樣的 3 個位元組,依查的對應表不同,會變成「に」「縺ォ」「ã«」。位元組序列連 1 個位元都沒有改變 — 這是最大的重點。
就算亂掉,資料也沒有壞
請再看一次上面的圖。E3 81 AB 這 3 個位元組,不管用哪種讀法,都維持完全相同的 3 個位元組。壞掉的不是資料,只是「讀法的選擇」。所以只要指定正確的編碼重新讀取,原本的「に」就會原封不動地回來。
不過有一件事必須注意。若以亂掉的樣子直接覆寫存檔,「縺ォ」這串亂掉的字元就會作為新的位元組序列寫入磁碟,原本的位元組序列就此消失。遇到亂碼時「先不要寫入,先懷疑讀法」— 這是實務上的第一個動作。
本課程的口訣:看到亂碼時請問自己 — 這是用哪種編碼寫入的位元組序列,被當成了哪種編碼來讀取?
小題 1-1 — 檔案的實體只有位元組序列
確認你能不能把亂碼的真面目,說成位元組序列與讀法之間的關係。
Q1. 以文字檔形式儲存在磁碟上的「實體」是什麼?
磁碟上存在的只有 0x00〜0xFF 的位元組排列。「這是 UTF-8」這種標籤原則上並不存在,讀取方要依某種根據(設定、宣告、猜測)自行決定編碼再讀取。正因如此,寫入方與讀取方的認知才有錯位的空間。碼位是號碼,實際儲存的是把它編碼後的位元組序列。
Q2. 同一串位元組 E3 81 AB,當作 UTF-8 讀是日文字元「に」,當作 Shift_JIS 讀則看起來是「縺ォ」。對這個現象的正確說明是哪一個?
所謂編碼,就是「位元組序列與字元的對應規則」本身。用 UTF-8 的規則查 E3 81 AB 會得到 U+306B「に」,用 Shift_JIS 的規則查,E3 81 是「縺」、AB 是半形片假名「ォ」。資料連 1 個位元都沒有壞。這與字型問題(俗稱「豆腐」的 □)是不同的現象,這個區別會在第 7 章再次處理。
Q3. 關於出現亂碼的檔案,多數情況下正確的說明是哪一個?
亂碼多半只是「讀法錯位」,位元組序列毫髮無傷。在編輯器重新指定編碼開啟,就能照原樣讀回來。不過要注意:若以亂掉的樣子直接「存檔」,亂掉的字串就會被寫成新的位元組序列,復原難度一口氣升高。「亂掉時先不要寫入,先懷疑讀法」是鐵則。
「編上號碼」與「變成位元組」— 分成 2 個階段思考
在這裡,我們引入貫穿整個課程的觀點。字元變成位元組序列之前,其實存在 2 個彼此獨立的階段。
「Unicode 和 UTF-8 到底差在哪?」這個經典疑問,用這 2 個階段就能立刻回答:Unicode 是第 1 階段(號碼的編法),UTF-8 是第 2 階段(號碼變成位元組的方法)。可以比喻成「決定門牌號碼的方式」與「把門牌號碼寫上信封的方式」的差別。第 2 章會仔細處理第 1 階段,第 3 章之後則仔細處理第 2 階段。
小題 1-2 — 區分字元集與編碼
確認貫穿本課程的 2 個階段 — 編上號碼(字元集)與變成位元組(編碼)— 的區別。
Q4. 「字元集」與「編碼」的分工,正確的是哪一個?
字元集(例:Unicode)是第 1 階段,像「あ = U+3042」那樣為字元編上號碼;編碼(例:UTF-8)是第 2 階段,像「U+3042 → E3 81 82」那樣把號碼轉換成位元組序列。歷史上也存在兩者一體的規格,但在 Unicode 的世界裡這 2 個階段被明確分開,分開思考後,關於亂碼的討論會一口氣變得清晰。
Q5. 「這個檔案是以 Unicode 儲存的」這種說法之所以不精確,原因是哪一個?
同樣是 Unicode 的號碼 U+3042,UTF-8 存成 E3 81 82、UTF-16 存成 30 42,儲存的位元組序列因編碼而異。「以 Unicode 儲存」無法確定第 2 階段,作為正確讀取檔案的資訊並不足夠。編輯器的存檔對話框裡並列的 UTF-8、UTF-16,正是這個第 2 階段的選項。
本章帶走的重點
- 檔案的實體只有位元組序列,上面沒有掛「是什麼編碼」的標籤
- 亂碼的真面目是「寫入的編碼」與「讀取的編碼」的錯位。位元組序列本身多半沒有壞,修正讀法就能還原(但不要在亂掉的狀態下存檔)
- 字元以號碼(碼位)→ 位元組序列(編碼)的 2 個階段來處理。Unicode 是第 1 階段,UTF-8 是第 2 階段
下一章將回顧第 1 階段「編上號碼」的歷史,從 ASCII 的 128 個字元,一路走到收納世界上所有字元的 Unicode。