首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 偵測檔案編碼並正確讀取:支援 UTF-8/16/32、GBK/Big5 自動識別

     編輯:Java 相關知識

前言

讀取未知編碼的文字檔是 Java 開發常見痛點。本文提供一個健壯的 CharsetDetector 工具類,採用「BOM 優先 → 啟發式掃描」雙重策略,可準確識別 UTF-8/16/32 (含 LE/BE/BOM)、GBK/GB2312、Big5 等常見編碼,並回傳標準 Charset 名稱供後續讀取使用。

核心演算法邏輯

  1. 讀取前 3 Bytes 檢查 BOM:
    • EF BB BF → UTF-8
    • FF FE → UTF-16LE
    • FE FF → UTF-16BE
    • 00 00 FE FF → UTF-32BE
    • FF FE 00 00 → UTF-32LE
  2. 無 BOM 時啟發式掃描:
    • 逐位元組分析 UTF-8 多位元組序列模式(導位元組 + 續位元組)。
    • 若出現非法 UTF-8 序列 → 判定為 GBK/Big5 (預設回傳 GBK)。
    • 掃描前 4KB 或全檔,平衡準確度與效能。

完整工具類實作

package 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) {
        // 處理每一行
    }
}

進階擴充建議

  • 整合 ICU4J / Apache Tika:生產環境建議引入 com.ibm.icu.text.CharsetDetector 或 org.apache.tika.parser.txt.CharsetDetector,準確率更高、支援超過 100 種編碼。
  • 大檔案優化:僅掃描前 64KB,避免完整讀取 GB 級日誌檔。
  • 中文編碼細分:GBK/GB2312/Big5/Big5-HKSCS 之間無 BOM 且位元組分佈相似,若需精確區分需結合字典/語言模型(如 juniversalchardet)。
  • Spring 整合:封裝為 @Component,注入 ResourceLoader 直接處理 classpath:、file: 資源。

注意事項

  1. 純 ASCII 檔案 (僅 0x00-0x7F) 無法區分編碼,預設回傳 GBK,實際讀取時 UTF-8/GBK 皆可正確解碼。
  2. 含中文但無 BOM 的 UTF-8 檔案,若剛好符合 GBK 雙位元組模式,極小機率誤判;建議生成檔案時務必加 BOM 或統一規範 UTF-8。
  3. Java 18+ file.encoding 預設 UTF-8,若專案仍相依 GBK,請啟動參數加 -Dfile.encoding=GBK 或程式碼明確指定編碼。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved