CodeGym /課程 /JAVA 25 SELF /編碼不一致的問題與常見錯誤

編碼不一致的問題與常見錯誤

JAVA 25 SELF
等級 37 , 課堂 3
開放

1. 錯誤的症狀

在理想世界裡,程式設計師總是知道檔案使用哪種編碼儲存,並在讀取時正確指定。但現實是——檔案在 Windows、Linux、伺服器與各種編輯器之間流轉,每個環境都可能用自己的方式解讀位元組。結果就會出現以下症狀:

  • 「亂碼」——原本應該是正常文字,卻看到奇怪符號、問號、方塊,或是看起來不像任何語言的字母組合。
  • 字元遺失——部分文字消失或被替換成 ?
  • 例外——例如,MalformedInputException,當 Java 無法用所選編碼「消化」這些位元組時。
  • 解析錯誤——由於文字被扭曲,關鍵字或結構受損,程式無法正確處理檔案。

以下是經典範例:在錯誤的編碼下讀取含西里爾字母的檔案時會看到的「亂碼」:

預期:  Privet, mir!
實際: Привет, мир

這不是新語言,而是因為位元組被用錯誤的「字典」來詮釋。

2. 為何會出錯:問題根源

檔案用一種編碼寫入,卻用另一種編碼讀取

假設有人把檔案存成 Windows-1251,而你用 UTF-8 開啟。Java 會忠實地依照 UTF-8 的規則解碼位元組,但結果會變得毫無意義,因為位元組的數值與預期不符。

使用系統的「預設」編碼

若你未明確指定編碼,Java 會使用系統編碼——也就是你電腦上設定的那個。在採用俄文語系的 Windows 上可能是 Windows-1251,在 Linux 上是 UTF-8,在 Mac 上通常也是 UTF-8。在你這裡能正常開啟的檔案,到了使用不同作業系統的同事那裡可能就讀不出來。

使用過時的建構子

在舊版 Java(以及某些教材)中,常能看到使用 FileReader/FileWriter 的寫法;它們使用系統編碼,無法讓你加以控制——這是陷阱,也是產生「亂碼」的來源。

FileReader reader = new FileReader("file.txt");
FileWriter writer = new FileWriter("file.txt");

是否存在 BOM (Byte Order Mark)

有些編碼(例如帶 BOMUTF-8UTF-16)會在檔案開頭加入特殊位元組,以表明自身性質。若程式沒有預期會有 BOM,或相反地——期望有它卻沒找到,就可能出問題:不是檔案的前幾個字元被扭曲,就是整個檔案無法被辨識。

3. 錯誤如何表現:實務解析

範例 1:含西里爾字母的檔案以 Windows-1251 寫入,卻用 UTF-8 讀取

import java.nio.file.*;
import java.nio.charset.*;

public class EncodingDemo {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("russian.txt");
        // 檔案以 Windows-1251 寫入,卻用 UTF-8 讀取 — 會出現亂碼!
        try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            System.out.println(reader.readLine());
        }
    }
}

結果你看到的不是「Privet, mir!」,而是一串怪異的符號。

範例 2:檔案以 UTF-8 寫入,卻用 ISO-8859-1 讀取

try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.ISO_8859_1)) {
    System.out.println(reader.readLine());
}

結果: 所有非 ASCII 字元都會變成垃圾或被替換為 ?

範例 3:讀取檔案時拋出例外

如果位元組不符合所選編碼的規則,Java 可能會拋出例外:

Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1
    at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    ...

這表示 Java 遇到了一個在該編碼下無法正確解讀的位元組。

4. 診斷:如何判斷編碼問題

檢查檔案的編碼

  • 在 Notepad++、VS Code 或 Sublime Text 等編輯器中,通常可以查看或更改檔案的編碼(一般在下方狀態列)。
  • 在 Linux 中可以用指令大致判斷檔案的編碼(不一定 100% 準確):
file filename.txt

檢查 Java 的系統編碼

在主控台列印屬性 file.encoding 的值:

System.out.println(System.getProperty("file.encoding"));

使用測試資料

建立一個包含多種字元(西里爾字母、拉丁字母、特殊符號、emoji)的小檔案,嘗試用不同編碼讀取,觀察何時結果與預期一致。

務必明確指定編碼

只要你看到在讀寫檔案時沒有指定編碼——就要提高警覺。例如,使用 Files.newBufferedReader(..., StandardCharsets.UTF_8) 來取代「預設值」。

5. 最佳實務:如何避免踩雷

規則 №1:
務必 在處理檔案時明確指定編碼,尤其當檔案會在不同電腦、不同作業系統之間使用,或透過網路傳輸時。

規則 №2:
使用現代且通用的編碼——首選 UTF-8StandardCharsets.UTF_8)。僅在有特殊需求(例如與舊系統整合)時,才使用其他編碼。

規則 №3:
避免使用 FileReaderFileWriter(它們無法指定編碼);改用 InputStreamReaderOutputStreamWriter,或 Files 中可明確傳入 Charset 的方法。

規則 №4:
檢查結果!用支援多種編碼的編輯器開啟你寫出的檔案,確認文字顯示正確。

6. 細節與眉角:BOM, XML, JSON 與其他「有趣」情況

BOM (Byte Order Mark):有時檔案在 UTF-8 中會以「看不見」的位元組(EF BB BF)開頭。多數現代程式會忽略它們,但也有些會在行首顯示「亂碼」,或拒絕該檔案(例如舊版的剖析器 XML/JSON)。

XML/HTML:有時檔案開頭會有一行像是 <?xml version="1.0" encoding="UTF-8"?> 的宣告。它告訴程式應以哪種編碼解讀位元組。但若實際編碼與宣告不一致——仍會出現「亂碼」。

JSON:按照標準應為 UTF-8,但若檔案其實是 Windows-1251,剖析器會拋出錯誤或產生失真的資料。

1
任務
JAVA 25 SELF, 等級 37, 課堂 3
上鎖
用錯誤的鏡片嘗試解讀古老卷軸 🔎
用錯誤的鏡片嘗試解讀古老卷軸 🔎
1
任務
JAVA 25 SELF, 等級 37, 課堂 3
上鎖
比較「翻譯器」分析:文本意義如何改變 🌍
比較「翻譯器」分析:文本意義如何改變 🌍
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION