CodeGym /課程 /C# SELF /編碼不匹配的麻煩以及 BOM

編碼不匹配的麻煩以及 BOM

C# SELF
等級 37 , 課堂 3
開放

1. 為什麼編碼一直那麼麻煩?

你應該已經知道:文字檔其實就是一串位元組而已。而 C#(以及 .NET)想要的是每個字都放在該放的位置。看起來好像只要在讀或寫的時候指定編碼就 OK,但現實世界沒那麼單純。

造成編碼混亂的原因:

  • 歷史遺留問題:檔案可能是由不同作業系統或編輯器產生,各自用的預設編碼不一樣。
  • 跨平台:在 Windows 建的檔案到 Linux 或 Mac 上讀,預設編碼常常不一樣。
  • BOM (Byte Order Mark):檔案開頭的「小抬頭」,有時有、有時沒,會影響程式怎麼看這個檔案。

2. 什麼是 BOM,為什麼會需要它

簡短說明 BOM

BOM (Byte Order Mark) 是放在檔案開頭的一段特殊位元組,告訴讀檔程式:「嗨,我是某個編碼,請用這個方式來解讀我的位元組」。

  • BOM 最常見於 UTF-8UTF-16UTF-32 的檔案。
  • UTF-8BOM 是可選的。它在或不在可能會影響不同程式怎麼讀這個檔案。
編碼 BOM(16 進位) 位元組
UTF-8
EF BB BF
239 187 191
UTF-16 LE
FF FE
255 254
UTF-16 BE
FE FF
254 255
UTF-32 LE
FF FE 00 00
255 254 0 0
UTF-32 BE
00 00 FE FF
0 0 254 255

小知識:ASCIIANSI 編碼裡面通常不會有 BOM。如果出現了,會把一些老程式弄得很驚訝。

示意:BOM 放哪裡


+--------------------------+
| BOM |     TEXT BYTES     |
+--------------------------+
|EFBBBF|  48 65 6C 6C 6F   |  // "Hello" 在 UTF-8 帶 BOM 的情況
+--------------------------+

EF BB BF — 這就是 UTF-8BOM,它在檔案最前面。
48 65 6C 6C 6F — 是文字 "Hello"UTF-8 下的位元組(如果沒有 BOM,這些位元組看起來一模一樣)。
總之:檔案是從 EF BB BF 開始,接著才是文字內容。

3. 編碼不匹配:亂碼從哪裡來

典型流程

  1. 你把檔案用 UTF-8 寫出,但是沒有帶 BOM
  2. 在 Windows 的某個編輯器打開,該編輯器預期的是 Windows-1251 或是帶 BOMUTF-8
  3. 結果 — 原本應該看到的 "嗨,世界!" 變成了 "Привет, РјРёСЂ!"(也就是亂碼)。

為什麼會這樣

  • 程式以為檔案是用某種編碼,但實際上位元組是用另一種編碼產生的。
  • BOM 可以幫忙猜出編碼,但如果沒有 BOM,很多程式就開始「亂猜」,結果就是看到亂碼。

不匹配的常見情境:

  • 你把 UTF-8 檔案當成 Windows-1251 去讀 — 非 ASCII 的字元就會變成亂碼。
  • 把帶 BOMUTF-8 當作「純」UTF-8 讀 — 大部分情況沒事,但一些老舊程式會把檔案前幾個字元顯示成奇怪符號。
  • 你寫檔時帶了 BOM,但對方期待沒有 BOM — 某些外部軟體不喜歡 BOM,可能會出錯。

4. 編碼和 BOM 對串流處理的影響

範例:用不同編碼寫跟讀檔案

// 寫入 UTF-8 檔案並帶 BOM
using var writer = new StreamWriter("test_utf8_bom.txt", false, new UTF8Encoding(true));
writer.WriteLine("嗨,世界!");

new UTF8Encoding(true) — 會加入 BOM

// 寫入 UTF-8 檔案但不帶 BOM
using var writer = new StreamWriter("test_utf8_no_bom.txt", false, new UTF8Encoding(false));
writer.WriteLine("嗨,世界!");

new UTF8Encoding(false) — 不會加入 BOM

// 用明確編碼讀檔
using var reader = new StreamReader("test_utf8_no_bom.txt", new UTF8Encoding(false));
string line = reader.ReadLine();
Console.WriteLine(line);

如果檔案是 UTF-8 且沒有 BOM,那麼明確指定編碼可以保證正確讀取。

常見錯誤

當你不指定編碼去讀檔時,StreamReader 會嘗試自己判斷 — 它會先看有沒有 BOM;如果沒有,就用系統預設(在 Windows 上俄文版常是 Windows-1251,在 Linux/Mac 則常是 UTF-8)。

5. 發現亂碼時該怎麼處理

你看到亂碼了。下一步怎麼做:

  1. 確認檔案是用什麼編碼建立的。
    用能顯示編碼的編輯器打開(例如 Notepad++)。
  2. 在讀/寫時明確指定編碼。
    不要把一切交給預設值,別以為「以前都可行」就安全:
using var reader = new StreamReader("data.txt", Encoding.UTF8);

記得考慮 BOM 的存在。

  • 如果別的軟體需要 BOM — 就加上它(參見 new UTF8Encoding(true))。
  • 如果不需要 — 就不要加(參見 new UTF8Encoding(false))。

範例:用錯誤編碼讀檔

// 檔案是用 UTF-8 建的,但我們用 Windows-1251 去讀
using var reader = new StreamReader("test_utf8_no_bom.txt", Encoding.GetEncoding(1251));
var text = reader.ReadToEnd();
Console.WriteLine(text); // 「嗨,世界!」會被破壞

6. 有用的小細節

BOM 在實務和面試中

如果你在一個專案裡面需要存設定檔、log 或者匯出資料,最好一開始就決定好用哪種編碼,並在文件裡寫清楚。這件事看起來很小,但當其他程式或其他人要讀你的檔案時,編碼不一致會造成一堆麻煩。

面試時,被問到 BOM 或「檔案開頭出現奇怪符號」是常見的題目。重要的不只是知道 BOM 是什麼,還要能解釋為什麼它的有/無會影響整合,和你該如何透過明確指定編碼來避免問題。

跟外部系統交換資料時要特別小心,因為不同作業系統或不同語言的 parser 行為可能不同。有些地方需要 BOM,有些地方卻會被它搞壞。如果不先預想到,之後要定位問題會很麻煩。

建議和最佳實務

  • 處理檔案時明確指定編碼(不要依賴預設值)。
  • 如果需要 BOM — 用 new UTF8Encoding(true);如果不需要 — 用 new UTF8Encoding(false)
  • 用能支援多種編碼的編輯器檢查檔案(例如 Notepad++、Visual Studio Code)。
  • 如果從外部拿到檔案,確認來源或文件裡標註的編碼。
  • 需要轉換編碼或移除 BOM 的時候就明確執行轉換。

不同程式會看到的情況

檔案 寫入時的編碼 用什麼去打開 會看到什麼
UTF-8 с BOM
UTF-8 + BOM
UTF-8
正常 ("嗨,世界!")
UTF-8 без BOM
UTF-8
Windows-1251
亂碼
Win-1251
Win-1251
UTF-8
亂碼
UTF-8 с BOM
UTF-8 + BOM
ASCII
檔案前幾個位元組被扭曲

7. 怎麼檢查和移除 BOM

範例:檢查 BOM 是否存在

byte[] bytes = File.ReadAllBytes("test_utf8_bom.txt");
// 檢查前面三個位元組
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
{
    Console.WriteLine("找到 BOM!這是帶 BOM 的 UTF-8。");
}
else
{
    Console.WriteLine("未找到 BOM。");
}

範例:移除 BOM(如果它造成問題)

if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
{
    // 把前 3 個位元組移掉再寫回檔案
    File.WriteAllBytes("no_bom.txt", bytes.Skip(3).ToArray());
}

8. 常見錯誤與解法

新手常常會傻眼,當程式在處理文字檔時忽然開始「怪怪的」。問題多半出在:寫檔的程式用一種編碼(或是有/無 BOM),讀檔的程式用另一種。例如:寫成 UTF-8(不帶 BOM),然後在 Windows 上用系統預設的 Windows-1251 去讀,結果當然會全變成亂碼。所以,務必要在處理檔案時明確指定編碼。如果檔案會在不同程式或不同平台間交換,推薦用通用的格式 — UTF-8(或在必要時使用帶 BOMUTF-8)。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION