讀取未知編碼的文字檔是 Java 開發常見痛點。本文提供一個健壯的 CharsetDetector 工具類,採用「BOM 優先 → 啟發式掃描」雙重策略,可準確識別 UTF-8/16/32 (含 LE/BE/BOM)、GBK/GB2312、Big5 等常見編碼,並回傳標準 Charset 名稱供後續讀取使用。
EF BB BF → UTF-8FF FE → UTF-16LEFE FF → UTF-16BE00 00 FE FF → UTF-32BEFF FE 00 00 → UTF-32LEpackage com.example.io;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public final class CharsetDetector {
private CharsetDetector() {}
/**
* 偵測檔案編碼,回傳標準 Charset 名稱 (可直接用於 new InputStreamReader)
* @param file 目標檔案
* @return 編碼名稱,無法判斷時預設 "GBK"
* @throws IOException 讀取失敗
*/
public static String detect(File file) throws IOException {
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis)) {
bis.mark(4);
byte[] bom = new byte[4];
int read = bis.read(bom);
if (read == -1) return "GBK"; // 空檔案
// 1. BOM 檢查
if (bom[0] == (byte)0xEF && bom[1] == (byte)0xBB && bom[2] == (byte)0xBF) {
return StandardCharsets.UTF_8.name();
}
if (bom[0] == (byte)0xFF && bom[1] == (byte)0xFE) {
if (read >= 4 && bom[2] == 0 && bom[3] == 0) return "UTF-32LE";
return "UTF-16LE";
}
if (bom[0] == (byte)0xFE && bom[1] == (byte)0xFF) {
if (read >= 4 && bom[2] == 0 && bom[3] == 0) return "UTF-32BE";
return "UTF-16BE";
}
if (read >= 4 && bom[0] == 0 && bom[1] == 0 && bom[2] == (byte)0xFE && bom[3] == (byte)0xFF) {
return "UTF-32BE";
}
if (read >= 4 && bom[0] == (byte)0xFF && bom[1] == (byte)0xFE && bom[2] == 0 && bom[3] == 0) {
return "UTF-32LE";
}
// 2. 無 BOM:啟發式 UTF-8 驗證
bis.reset();
return heuristicUtf8OrGbk(bis) ? StandardCharsets.UTF_8.name() : "GBK";
}
}
/**
* 掃描位元組流,判斷是否符合 UTF-8 編碼規則
*/
private static boolean heuristicUtf8OrGbk(BufferedInputStream bis) throws IOException {
int b;
while ((b = bis.read()) != -1) {
if (b >= 0xF0) return false; // 4+ bytes UTF-8 極少見,視為非 UTF-8
if (0x80 <= b && b <= 0xBF) return false; // 孤立續位元組
if (0xC0 <= b && b <= 0xDF) { // 2-byte sequence
int b2 = bis.read();
if (b2 == -1 || !(0x80 <= b2 && b2 <= 0xBF)) return false;
} else if (0xE0 <= b && b <= 0xEF) { // 3-byte sequence
int b2 = bis.read();
int b3 = bis.read();
if (b2 == -1 || b3 == -1) return false;
if (!(0x80 <= b2 && b2 <= 0xBF && 0x80 <= b3 && b3 <= 0xBF)) return false;
}
// ASCII (0x00-0x7F) 繼續掃描
}
return true; // 全部通過 → 高機率 UTF-8
}
/** 便利方法:直接以偵測編碼讀取整個檔案內容 */
public static String readToString(File file) throws IOException {
String encoding = detect(file);
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(file), encoding))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append(System.lineSeparator());
}
return sb.toString();
}
}
public static void main(String[] args) throws IOException {
File test = new File("test.txt");
System.out.println("Detected: " + detect(test));
System.out.println("Content:\n" + readToString(test));
}
}
File csv = new File("data/export.csv");
String encoding = CharsetDetector.detect(csv); // 例: "UTF-8" 或 "GBK"
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(csv), encoding))) {
String line;
while ((line = br.readLine()) != null) {
// 處理每一行
}
}
com.ibm.icu.text.CharsetDetector 或 org.apache.tika.parser.txt.CharsetDetector,準確率更高、支援超過 100 種編碼。juniversalchardet)。@Component,注入 ResourceLoader 直接處理 classpath:、file: 資源。file.encoding 預設 UTF-8,若專案仍相依 GBK,請啟動參數加 -Dfile.encoding=GBK 或程式碼明確指定編碼。