承接上一篇偵測工具,本文聚焦「如何在實際讀取流程中無縫整合編碼偵測」,提供三種整合模式:工具類直呼、InputStreamReader 裝飾器、Spring Resource 擴充,並針對大檔案、串流處理、記憶體效能給出最佳化建議。
String encoding = CharsetDetector.detect(file);
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(file), encoding))) {
// 正常逐行處理
}
package com.example.io;
import java.io.*;
public class AutoDetectReader extends Reader {
private final Reader delegate;
public AutoDetectReader(InputStream in) throws IOException {
// 先用 BufferedInputStream 偵測
BufferedInputStream bis = new BufferedInputStream(in);
bis.mark(4096);
String encoding = CharsetDetector.detectStream(bis); // 需新增靜態方法接受 InputStream
bis.reset();
this.delegate = new InputStreamReader(bis, encoding);
}
@Override public int read(char[] cbuf, int off, int len) throws IOException {
return delegate.read(cbuf, off, len);
}
@Override public void close() throws IOException {
delegate.close();
}
// 其他 read/ready/skip 直接委派...
}
使用方式:try (Reader r = new AutoDetectReader(new FileInputStream(file))) { ... }
@Component
public class EncodingAwareResourceReader {
public String readAsString(Resource resource) throws IOException {
try (InputStream is = resource.getInputStream()) {
BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(4096);
String encoding = CharsetDetector.detectStream(bis);
bis.reset();
return new String(bis.readAllBytes(), encoding); // JDK 9+
}
}
public Stream<String> lines(Resource resource) throws IOException {
InputStream is = resource.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
bis.mark(4096);
String encoding = CharsetDetector.detectStream(bis);
bis.reset();
return new BufferedReader(new InputStreamReader(bis, encoding)).lines();
}
}
控制器注入:@Autowired EncodingAwareResourceReader reader;Stream<String> lines = reader.lines(new ClassPathResource("data.csv"));
| 場景 | 建議做法 | 關鍵點 |
|---|---|---|
| GB 級日誌分析 | 偵測前 64KB → 固定編碼 → Files.lines() 串流 | 避免全檔掃描;串流處理不載入記憶體 |
| CSV/TSV 匯入 | 偵測編碼 → CSVParser (Apache Commons CSV) | Parser 直接接受 Reader,無缝銜接 |
| XML/JSON 解析 | 偵測編碼 → DocumentBuilder/JsonParser 接收 InputStream | 解析器內部處理編碼,僅需正確開啟 Stream |
| 批次轉碼 | 偵測 → Files.readAllBytes() → new String(bytes, srcEnc) → getBytes(UTF-8) → 寫入 | 一次性轉碼存檔,後續統一 UTF-8 |
public static String detectStream(BufferedInputStream bis) throws IOException {
bis.mark(4);
byte[] bom = new byte[4];
int read = bis.read(bom);
if (read == -1) return "GBK";
// ... BOM 檢查邏輯同 detect(File) ...
bis.reset();
return heuristicUtf8OrGbk(bis) ? StandardCharsets.UTF_8.name() : "GBK";
}
| 方式 | 堆外記憶體 | 堆記憶體峰值 | 耗時 |
|---|---|---|---|
| readAllBytes + new String | ~100MB | ~200MB (byte[] + char[]) | ~800ms |
| BufferedReader 逐行 | ~8KB | ~1MB | ~900ms |
| Files.lines() 串流 | ~8KB | ~1MB | ~850ms |
結論:大檔案務必使用串流/逐行處理,避免 readAllBytes 造成 OOM。
EF BB BF) 若未移除,首行會帶入 \uFEFF。解法:偵測到 UTF-8 BOM 時,bis.skip(3) 或讀取首行後 line.replace("\uFEFF", "")。StandardCharsets.UTF_8。\uFEFF 零寬空白的檔案建議將上述案例納入單元測試,確保升級 JDK/依賴庫後不退化。