1. 前言
直說吧:如果你曾在俄文文字的位置看到像 Привет 這樣的字串,你已經是編碼錯誤的受害者。這通常發生在檔案以某種編碼寫入,卻用另一種編碼讀取。例如,檔案存成 UTF-8,卻以 Windows-1251 讀取,或反之亦然。
Java 預設 會使用系統編碼,你可以這樣查:
System.out.println(System.getProperty("file.encoding"));
在某些電腦上可能是 UTF-8,在另一些則是 Windows-1251,還有些會是 ISO-8859-1。因此,最好永遠明確指定編碼。這在你處理多語系資料、檔案會在不同電腦或其他程式中使用、或希望你的程式在任何環境都表現一致(而不是只在你的機器上正常)時尤其重要。
Charset 類:編碼世界中的好夥伴
在 Java 中,處理編碼要使用 java.nio.charset.Charset。 它允許以名稱指定編碼(例如 "UTF-8"),也可以使用標準常數(StandardCharsets.UTF_8)。
常見標準編碼範例:
| 編碼 | Java 常數 |
|---|---|
| UTF-8 | |
| UTF-16 | |
| ISO-8859-1 | |
| Windows-1251 | |
建議優先使用常數:比較不容易拼錯名稱,也不會拋出 UnsupportedCharsetException。
2. 指定編碼讀取檔案
舊式作法:
import java.io.*;
import java.nio.charset.StandardCharsets;
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("example.txt"),
StandardCharsets.UTF_8 // <-- 明確指定編碼
)
);
現代作法:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedReader;
BufferedReader reader = Files.newBufferedReader(
Paths.get("example.txt"),
StandardCharsets.UTF_8 // <-- 明確指定編碼
);
建議使用第二種方式——更簡短、更安全,且與 try-with-resources 很好搭配。
範例:從檔案讀取一行
try (BufferedReader reader = Files.newBufferedReader(
Paths.get("hello.txt"),
StandardCharsets.UTF_8)) {
String line = reader.readLine();
System.out.println("讀到:" + line);
}
為什麼這很重要: 如果檔案以 UTF-8 儲存,卻用 Windows-1251 讀取,西里爾字元會變形成亂碼。只要指定正確的編碼,文字在任何作業系統上都能被正確讀取。
3. 指定編碼寫入檔案
舊式作法:
import java.io.*;
import java.nio.charset.StandardCharsets;
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("example.txt"),
StandardCharsets.UTF_8 // <-- 明確指定編碼
)
);
現代作法:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.BufferedWriter;
BufferedWriter writer = Files.newBufferedWriter(
Paths.get("example.txt"),
StandardCharsets.UTF_8 // <-- 明確指定編碼
);
範例:將一行寫入檔案
try (BufferedWriter writer = Files.newBufferedWriter(
Paths.get("hello.txt"),
StandardCharsets.UTF_8)) {
writer.write("你好,世界!");
}
結果: 檔案會以 UTF-8 儲存,且可在任何支援 UTF-8 的編輯器中正確開啟。
4. 有用的細節
如何查出支援哪些編碼
import java.nio.charset.Charset;
public class ListCharsets {
public static void main(String[] args) {
System.out.println("可用的編碼:");
Charset.availableCharsets().forEach((name, charset) -> System.out.println(name));
}
}
建議: 如果你使用了較為冷門的編碼(例如古漢字或外星表情符號),請確認你的 JVM 是否支援。
使用 try-with-resources:別忘了關閉串流
處理檔案時,務必關閉串流以避免資源外洩。現代的 Java 程式會使用 try-with-resources 結構:
try (BufferedReader reader = Files.newBufferedReader(path, charset)) {
// 處理檔案
}
就算發生錯誤,串流也會自動關閉。
建議
- 最好一律明確指定編碼 在讀寫檔案時,即使你覺得「預設也能正常運作」。
- 新檔案請使用 UTF-8——這已是事實上的標準,尤其在 web、JSON、XML 等情境,或希望你的檔案到處都可讀取時。
- 對於舊檔案(例如 1C 的匯出、舊資料庫、Windows 的 CSV),請使用它們當初儲存時的編碼(例如 Windows-1251、ISO-8859-1)。
- 不要使用過時的類別,因為無法明確指定編碼:FileReader/FileWriter。改用 InputStreamReader/OutputStreamWriter 並明確指定編碼,或使用 Files 的方法。
- 處理大型檔案時 請使用緩衝(BufferedReader/BufferedWriter),以免耗盡記憶體。
5. 編碼相關的常見錯誤
錯誤 №1:讀寫檔案時未指定編碼。
若未指定,Java 會使用系統預設("file.encoding")。在你的電腦上可能沒問題,但同事那邊就會變成「亂碼」。
錯誤 №2:讀寫使用了不同的編碼。
檔案以一種編碼寫入,卻以另一種編碼讀取。例如,檔案以 UTF-8 寫入,卻以 Windows-1251 讀取——西里爾字元會被扭曲。
錯誤 №3:使用過時的 FileReader/FileWriter 類別。
這些類別無法明確指定編碼——不建議使用。請改用 InputStreamReader/OutputStreamWriter 並指定編碼,或使用 Files 的方法。
錯誤 №4:編碼名稱拼寫錯誤。
例如寫成 "utf8" 而不是 "UTF-8",或 "win1251" 而不是 "Windows-1251"。Java 會拋出 UnsupportedCharsetException。
錯誤 №5:未關閉串流——檔案未完整寫入。
如果不使用 try-with-resources 或未明確關閉串流,部分資料可能無法寫入到磁碟。
GO TO FULL VERSION