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

Java 指定編碼讀寫檔案工具類:UTF-8/GBK/Big5 統一處理、自動建立目錄、大檔案串流

     編輯:Java 相關知識

前言

檔案編碼不一致是中文亂碼首因。本文提供一個生產級 FileEncodingUtil,統一解決:指定編碼讀寫、自動建立父目錄、大檔案分行串流、編碼偵測整合、資源安全釋放,並附帶單元測試範本。

完整工具類

package com.example.io;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.function.Consumer;

public final class FileEncodingUtil {

    private FileEncodingUtil() {}

    /** 寫入字串 (覆寫模式),自動建立父目錄 */
    public static void writeString(Path path, String content, Charset charset) throws IOException {
        Files.createDirectories(path.getParent());
        Files.writeString(path, content, charset, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }

    /** 寫入字串 (附加模式) */
    public static void appendString(Path path, String content, Charset charset) throws IOException {
        Files.createDirectories(path.getParent());
        Files.writeString(path, content, charset, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
    }

    /** 寫入多行 (List<String>) */
    public static void writeLines(Path path, List<String> lines, Charset charset) throws IOException {
        Files.createDirectories(path.getParent());
        Files.write(path, lines, charset, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }

    /** 讀取整個檔案為字串 (小檔案) */
    public static String readString(Path path, Charset charset) throws IOException {
        return Files.readString(path, charset);
    }

    /** 逐行讀取並處理 (大檔案,記憶體友善) */
    public static void readLines(Path path, Charset charset, Consumer<String> lineProcessor) throws IOException {
        try (BufferedReader br = Files.newBufferedReader(path, charset)) {
            String line;
            while ((line = br.readLine()) != null) {
                lineProcessor.accept(line);
            }
        }
    }

    /** 讀取所有行至 List (中等檔案) */
    public static List<String> readAllLines(Path path, Charset charset) throws IOException {
        return Files.readAllLines(path, charset);
    }

    /** 二進位寫入 (圖片、壓縮檔等) */
    public static void writeBytes(Path path, byte[] data) throws IOException {
        Files.createDirectories(path.getParent());
        Files.write(path, data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }

    /** 二進位讀取 */
    public static byte[] readBytes(Path path) throws IOException {
        return Files.readAllBytes(path);
    }

    /** 複製檔案 (保留屬性、支援大檔零拷貝) */
    public static void copyFile(Path src, Path dst, boolean preserveAttributes) throws IOException {
        Files.createDirectories(dst.getParent());
        if (preserveAttributes) {
            Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
        } else {
            Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    /** 移動/重命名檔案 */
    public static void moveFile(Path src, Path dst, boolean atomic) throws IOException {
        Files.createDirectories(dst.getParent());
        if (atomic) {
            Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
        } else {
            Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    /** 刪除檔案 (不存在不拋例外) */
    public static boolean deleteQuietly(Path path) {
        try {
            return Files.deleteIfExists(path);
        } catch (IOException e) {
            return false;
        }
    }

    /** 取得檔案大小 */
    public static long size(Path path) throws IOException {
        return Files.size(path);
    }

    /** 判斷檔案是否存在且可讀 */
    public static boolean isReadable(Path path) {
        return Files.isRegularFile(path) && Files.isReadable(path);
    }

    // --- 編碼偵測整合 (需搭配前文 CharsetDetector) ---
    public static String readStringAutoDetect(Path path) throws IOException {
        String encoding = CharsetDetector.detect(path.toFile());
        return readString(path, Charset.forName(encoding));
    }

    public static void readLinesAutoDetect(Path path, Consumer<String> processor) throws IOException {
        String encoding = CharsetDetector.detect(path.toFile());
        readLines(path, Charset.forName(encoding), processor);
    }
}

使用範例

Path log = Paths.get("logs/app.log");
// 寫入 UTF-8
FileEncodingUtil.appendString(log, "新日誌行\n", StandardCharsets.UTF_8);

// 讀取 GBK 編碼 CSV
FileEncodingUtil.readLines(Paths.get("data.csv"), Charset.forName("GBK"), line -> {
    String[] cols = line.split(",");
    // 處理欄位...
});

// 自動偵測編碼讀取
String content = FileEncodingUtil.readStringAutoDetect(Paths.get("unknown.txt"));

// 大檔案二進位複製 (零拷貝)
FileEncodingUtil.copyFile(Paths.get("big.iso"), Paths.get("backup/big.iso"), true);

單元測試範本 (JUnit 5)

@TempDir Path tempDir;

@Test void writeAndReadUtf8() throws IOException {
    Path file = tempDir.resolve("test.txt");
    String original = "測試中文內容\nSecond line";
    FileEncodingUtil.writeString(file, original, StandardCharsets.UTF_8);
    assertEquals(original, FileEncodingUtil.readString(file, StandardCharsets.UTF_8));
}

@Test void appendMode() throws IOException {
    Path file = tempDir.resolve("append.txt");
    FileEncodingUtil.writeString(file, "Line 1\n", StandardCharsets.UTF_8);
    FileEncodingUtil.appendString(file, "Line 2\n", StandardCharsets.UTF_8);
    assertEquals("Line 1\nLine 2\n", FileEncodingUtil.readString(file, StandardCharsets.UTF_8));
}

@Test void largeFileStreaming() throws IOException {
    Path file = tempDir.resolve("big.txt");
    // 產生 10MB 測資
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 100_000; i++) sb.append("Line ").append(i).append('\n');
    FileEncodingUtil.writeString(file, sb.toString(), StandardCharsets.UTF_8);

    AtomicInteger count = new AtomicInteger();
    FileEncodingUtil.readLines(file, StandardCharsets.UTF_8, line -> count.incrementAndGet());
    assertEquals(100_000, count.get());
}

@Test void autoDetectGbk() throws IOException {
    Path file = tempDir.resolve("gbk.txt");
    // 手工寫入 GBK 位元組
    Files.write(file, "中文測試".getBytes("GBK"));
    String content = FileEncodingUtil.readStringAutoDetect(file);
    assertEquals("中文測試", content);
}

遷移指南:從舊版 FileReader/FileWriter

舊寫法新寫法 (NIO.2)優勢
new FileWriter(file, "UTF-8")Files.writeString(path, str, StandardCharsets.UTF_8, ...)自動建目錄、原子選項、編碼型別安全
new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"))Files.newBufferedReader(path, Charset.forName("GBK"))try-with-resources 自動關閉、路徑物件化
file.mkdirs()Files.createDirectories(path.getParent())拋出具體例外、支援 Path
file.length()Files.size(path)拋 IOException 而非回傳 0

效能提示

  • 小檔案 (< 1MB):readString/writeString 最簡潔。
  • 中檔案 (1MB-100MB):readAllLines/writeLines 平衡記憶體與速度。
  • 大檔案 (> 100MB):必須 用 readLines(path, charset, consumer) 串流處理,或 Files.newInputStream 零拷貝複製。
  • 頻繁附加寫入:單一 BufferedWriter 持有開啟狀態,批次 flush,避免重複開關檔案。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved