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

Java IO 文件編碼轉換實戰:自動檢測源編碼並安全轉 UTF-8

     編輯:Java 相關知識

前言

文件編碼不一致是亂碼高發區。本文實現一個健壯的 EncodingConverter 工具類:自動檢測源文件編碼 (借助 juniversalchardet 或 CPDetector)、按行讀取轉碼、原子性寫入 (臨時文件 + 重命名)、完整異常回滾機制。

依賴引入

<dependency>
    <groupId>com.googlecode.juniversalchardet</groupId>
    <artifactId>juniversalchardet</artifactId>
    <version>1.0.3</version>
</dependency>
<!-- 或使用 Mozilla 通用檢測庫 -->

完整工具類

import org.mozilla.universalchardet.UniversalDetector;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

public class EncodingConverter {

    /** 檢測文件編碼 */
    public static String detectEncoding(File file) throws IOException {
        byte[] buf = new byte[4096];
        UniversalDetector detector = new UniversalDetector(null);
        try (FileInputStream fis = new FileInputStream(file)) {
            int nread;
            while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
                detector.handleData(buf, 0, nread);
            }
        }
        detector.dataEnd();
        String encoding = detector.getDetectedCharset();
        detector.reset();
        return encoding != null ? encoding : "UTF-8"; // 默認兜底
    }

    /** 轉換文件編碼至目標編碼 (默認 UTF-8) */
    public static void convertTo(File srcFile, String targetEncoding) throws IOException {
        String srcEncoding = detectEncoding(srcFile);
        if (srcEncoding.equalsIgnoreCase(targetEncoding)) {
            System.out.println("編碼一致,無需轉換: " + srcFile.getName());
            return;
        }

        File tmpFile = File.createTempFile("conv_", ".tmp", srcFile.getParentFile());
        try (
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(srcFile), srcEncoding));
            BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(tmpFile), targetEncoding))
        ) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine(); // 保持系統換行符
            }
            writer.flush();
        }

        // 原子性替換
        Files.move(tmpFile.toPath(), srcFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        System.out.println("轉換完成: " + srcFile.getName() + " (" + srcEncoding + " -> " + targetEncoding + ")");
    }

    /** 讀取文件內容並按目標編碼返回字符串 (不修改原文件) */
    public static String readAsString(File file, String targetEncoding) throws IOException {
        String srcEncoding = detectEncoding(file);
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(file), srcEncoding))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append(System.lineSeparator());
            }
        }
        // 重新編碼為目標編碼字節再解碼 (雙重保險)
        return new String(sb.toString().getBytes(targetEncoding), targetEncoding);
    }

    public static void main(String[] args) {
        try {
            File testFile = new File("D:/data/gbk_file.txt");
            convertTo(testFile, "UTF-8");
            // 或僅讀取
            String content = readAsString(testFile, "UTF-8");
            System.out.println(content.substring(0, Math.min(200, content.length())));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

關鍵技術點

  1. 編碼檢測:UniversalDetector 基於 Mozilla 算法,支援 GBK/Big5/Shift_JIS/UTF-8/UTF-16 等主流編碼,準確率極高。
  2. 流式處理:BufferedReader/Writer 按行處理,常數內存,支援超大文件 (GB 級)。
  3. 原子性寫入:寫入臨時文件,成功後 Files.move(REPLACE_EXISTING) 原子替換,避免寫入中斷損壞源文件。
  4. 換行符保持:writer.newLine() 自動使用系統默認換行符 (Windows \r\n, Linux \n),或可顯式指定。
  5. BOM 處理:UTF-8 BOM (EF BB BF) 會被檢測為 UTF-8,讀取時自動跳過;寫入 UTF-8 默認不加 BOM,如需保留可手動寫入。

常見坑點與對策

問題原因解決
檢測結果為 null文件過短/純 ASCII默認 UTF-8 或嘗試指定常見編碼
轉換後中文亂碼源編碼檢測錯誤 (如 GBK 誤判為 ISO-8859-1)強制指定源編碼;或檢查文件是否混合編碼
大文件 OOM一次性 readAllBytes必須使用 BufferedReader 流式讀寫
權限拒絕目標文件被佔用/只讀檢查 file.canWrite();關閉佔用進程

進階:批量轉換目錄

public static void convertDirectory(File dir, String targetEncoding) throws IOException {
    File[] files = dir.listFiles((d, name) -> name.endsWith(".txt") || name.endsWith(".java") || name.endsWith(".xml"));
    if (files == null) return;
    for (File f : files) {
        if (f.isFile()) convertTo(f, targetEncoding);
        else convertDirectory(f, targetEncoding);
    }
}

總結

文件編碼轉換看似簡單,實則暗藏「檢測準確性、內存安全性、寫入原子性」三大挑戰。採用 juniversalchardet 檢測 + BufferedReader/Writer 流式轉碼 + Files.move 原子替換的組合拳,可構建生產級可靠轉換工具。原則:不信任扩展名,不信任默認編碼,不原地覆蓋源文件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved