UTF-8 解碼是將一連串位元組轉回其所代表的原始 Unicode 字元的過程,而嚴格的解碼器會在遇到格式錯誤的位元組序列時明確回傳錯誤,而不是以 U+FFFD 替代字元取代它們。UTF-8 是由 RFC 3629 定義的可變寬度編碼,因此每個 Unicode 純量值會使用 1 到 4 個位元組,並有特定的引導位元組與接續位元組模式。當你手上有一串以十六進位、十進位或 8 位元二進位標記呈現的位元組時,只有在每個位元組都符合其在模式中的角色時,才能將它們解碼回文字。挑戰在於許多位元組序列在視覺上看似合理,但數學上是無效的,包括過長編碼、被截斷的多位元組序列、孤立的接續位元組、代理碼點,以及超過 U+10FFFF 的值。安全的解碼工作流程會將上述任何一種情況視為嚴重失敗,避免將猜測出來的文字當作已驗證的資料呈現出來。這正是 UTF-8 編碼器 / 解碼器 在你的瀏覽器中以本地端方式執行的工作,它使用平台提供的 fatal TextDecoder,讓錯誤的位元組以錯誤形式呈現,而不是變成替代字元。

utf8 decode
解碼 UTF-8 位元組時避免出現靜默的替代字元

為什麼 UTF-8 解碼在現實世界中會出錯

UTF-8 乍看之下很簡單,但位元組模式是不容許錯誤的。一個有效的 UTF-8 串流是一連串的位元組,其中每個引導位元組會宣告其後接續位元組的數量,而每個接續位元組都以位元 10 開頭。當任何位元組違反這個約定時,該串流就不再是格式正確的 UTF-8,即使這些位元組恰好能對應到另一種編碼中的可列印字元。

在日誌檔、網路擷取資料和匯出資料中,有幾種特定的失敗模式會反覆出現:

  • 在區塊邊界處發生截斷序列,例如在傳輸被切分時出現位元組 E2 82 但缺少第三個位元組。
  • 當引導位元組遺失或被轉換後,留下孤立的接續位元組。
  • 使用比必要更多的位元組進行過長編碼,例如用 C0 AF 表示斜線字元。
  • U+D800 到 U+DFFF 範圍內的代理碼點,這些保留給 UTF-16 使用,絕不應出現在 UTF-8 中。
  • 超過 U+10FFFF 的純量值,這些完全超出了已定義的 Unicode 範圍。

非嚴格的解碼器會將上述每一種失敗都代換為 U+FFFD,導致輸出中出現一連串的替代字元。這會隱藏原始問題,因為使用者看到的文字幾乎可以讀取,可能不會察覺到位元組已經損壞。嚴格的 UTF-8 解碼則會在看到無效序列的當下立即回傳明確的錯誤,這是確保你讀到的內容確實來自原始來源而非猜測的唯一方法。

你可以解碼的三種位元組表示法

在解碼之前,你需要先知道來源資料使用的是哪一種表示法。UTF-8 本身是單一的位元組序列,但人類幾乎從不直接閱讀原始位元組,因此這些位元組會以下列三種表示法之一來書寫:

  • 十六進位使用大寫的兩位數位元組,並以空格分隔(例如 24 41 C2 A2)。它也接受以空格或逗號分隔、帶有選用 0x 前綴的一位或兩位數位元組標記,或是一段連續的偶數長度十六進位字串,例如 2441C2A2。
  • 十進位使用 0 到 255 的整數標記,並以空格分隔(例如 36 65 194 162)。它只接受整數標記,不會解讀字母。
  • 二進位每個標記必須恰好是八個 0 或 1 字元(例如 00100100 01000001 11000010 10100010)。

這三種是同一個位元組陣列的不同檢視方式。改變表示法並不會改變底層的 UTF-8 序列,因此正確的解碼無論你從哪一種表示法開始,都會產生相同的文字。選擇正確的表示法只對解析器有意義:十六進位允許連續字串與 0x 前綴,十進位接受整數標記,二進位則嚴格要求每個標記恰好為八個位元。如果你貼上的位元組格式是解析器無法辨識的,解碼會在標記階段就失敗,甚至還不會檢查位元組序列本身。

如何逐步解碼 UTF-8 位元組

  1. 在瀏覽器分頁中開啟 UTF-8 編碼器 / 解碼器。
  2. 選擇 UTF-8 位元組轉文字方向,讓工具知道你要進行解碼而非編碼。
  3. 選擇與你輸入內容相符的表示法:十六進位、十進位或二進位。如果你不確定,可以先從十六進位開始,因為它接受連續字串、以空格分隔的標記、逗號分隔符,以及選用的 0x 前綴。
  4. 將位元組序列貼到輸入區域中。解析器接受以空格或逗號分隔、可選用 0x 前綴的標記,以及一段連續的偶數長度十六進位字串。
  5. 選擇轉換按鈕來執行解碼。轉換完全在目前分頁中執行,因此不會上傳或儲存任何資料。
  6. 將解碼後的文字與原始來源格式進行比對。如果來源標示為 UTF-8 且輸出相符,則來回轉換是正確的。
  7. 為了確認結果是穩定的,請將解碼後的文字重新編碼回位元組,並檢查取得的位元組序列是否與你一開始的完全相同,再決定是否覆寫原始資料。

此工具的上限為 200,000 位元組的解碼表示法,以及 200,000 個 UTF-16 碼位的文字,因此請將你的輸入控制在這些限制之內。如果你有更大的檔案,請使用專門從磁碟串流讀取而非將所有內容載入記憶體的二進位工具。

常見字元的參考位元組模式

每個有效的 UTF-8 字元都遵循四種位元組長度模式之一,而引導位元組永遠會宣告使用的是哪一種模式。下方的參考表使用來自 RFC 3629 與 Unicode 標準核心規格 的已驗證數值。

碼點字元位元組長度UTF-8 位元組(十六進位)
U+0024$(美元符號)124
U+0041A(拉丁字母 A)141
U+007FDEL(邊界)17F
U+00A2¢(分符號)2C2 A2
U+0800薩馬利亞字母邊界3E0 A0 80
U+20AC€(歐元符號)3E2 82 AC
U+1F600😀(露齒笑臉)4F0 9F 98 80
U+10FFFF最大純量值4F4 8F BF BF

美元符號與拉丁字母 A 落在 1 位元組的 ASCII 範圍內,分符號跨越 U+0080 邊界進入 2 位元組範圍,歐元符號使用 3 個位元組,而露齒笑臉表情符號則用滿了 4 個位元組。最大純量值 U+10FFFF 會編碼為 F4 8F BF BF,這是在該標準下永遠能存在的最大格式正確 UTF-8 序列。

解碼失敗時會發生什麼事

嚴格的 UTF-8 解碼器會在遇到錯誤輸入時大聲失敗,而不是猜測。下表中每一列都描述一種輸入形式,以及當你將其貼進 fatal 解碼器時應該預期看到的失敗情況。

輸入形式範例位元組失敗模式
過長編碼C0 AF拒絕:斜線不需要 2 個位元組。
截斷序列E2 82拒絕:引導位元組宣告需要 3 個位元組,但只出現 2 個。
孤立接續位元組單獨的 80拒絕:接續位元組沒有對應的引導位元組。
代理編碼ED A0 80拒絕:U+D800 到 U+DFFF 並非有效的 UTF-8 純量。
超出範圍F5 80 80 80拒絕:編碼出的值超過 U+10FFFF。
空輸入(無)解碼為空文字且不會產生錯誤。

相同的規則也適用於反向的編碼作業。如果輸入文字包含未配對的 UTF-16 代理碼元,工具會在呼叫 TextEncoder 之前將其拒絕,這樣宣稱無損的轉換就不會在不知情的情況下改變原始輸入。代表實際補充字元的有效代理配對(例如表情符號)會被接受,並正確編碼為 4 個 UTF-8 位元組。

在信任結果之前驗證來回轉換

單一次的解碼過程能告訴你這些位元組應該呈現什麼內容,但無法證明你一開始就擁有正確的位元組。標準的紀律是進行來回轉換檢查:將位元組解碼為文字,然後將同一段文字重新編碼回位元組,並比對新的位元組序列與原始序列。如果兩組位元組序列相符,則解碼是無損的,且表示法是一致的。如果兩者不同,表示你一開始就擁有格式錯誤的位元組,或是你為解析器選錯了表示法。

請先在一小段已知的範例上執行來回轉換。挑選幾個具代表性的字元——包括一個 ASCII 字元、一個 2 位元組字元、一個 3 位元組字元,以及一個 4 位元組的表情符號——就足以暴露位元組邊界上大部分的錯誤。只有在來回轉換成功之後,才應該替換掉原始來源。UTF-8 瀏覽器工具的來回轉換驗證指南對此紀律有更深入的探討,包括如何在取得已驗證的輸出之前保持原始資料的完整性。

如果這些位元組來自可能使用 Windows-1252、Shift JIS、GBK 或任何 ISO-8859 系列的系統,請勿強制將它們以 UTF-8 處理。本頁面僅支援 UTF-8,且會拒絕那些看似有效但解碼出來變成亂碼的位元組——這正是用來識別原始編碼的線索,而非用猜測的方式繼續處理。

想進一步了解,請參閱 Vigenere 密碼解碼器大量解碼:解碼長篇密文。