Java 寫入檔案

Java 提供多種方式寫入檔案,適用於不同的使用場景。

使用 Files(推薦)

Java 7+ 的 Files 類別提供簡潔的 API:

寫入字串

import java.nio.file.Files;
import java.nio.file.Paths;

// 寫入字串(Java 11+)
Files.writeString(Paths.get("file.txt"), "Hello, World!");

// 附加模式
Files.writeString(Paths.get("file.txt"), "New content", 
    StandardOpenOption.APPEND);

// 指定選項
Files.writeString(Paths.get("file.txt"), "Content",
    StandardOpenOption.CREATE,
    StandardOpenOption.TRUNCATE_EXISTING);

寫入位元組

byte[] data = "Hello".getBytes(StandardCharsets.UTF_8);
Files.write(Paths.get("file.txt"), data);

// 附加模式
Files.write(Paths.get("file.txt"), data, StandardOpenOption.APPEND);

寫入多行

List<String> lines = Arrays.asList("Line 1", "Line 2", "Line 3");
Files.write(Paths.get("file.txt"), lines);

// 指定編碼
Files.write(Paths.get("file.txt"), lines, StandardCharsets.UTF_8);

使用 BufferedWriter

適合寫入大量文字:

import java.io.BufferedWriter;
import java.io.FileWriter;

// 基本用法
try (BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"))) {
    writer.write("Hello, World!");
    writer.newLine();  // 寫入換行
    writer.write("Second line");
}

// 附加模式
try (BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt", true))) {
    writer.write("Appended content");
}

// 使用 Files.newBufferedWriter(推薦)
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("file.txt"))) {
    writer.write("Content");
}

// 指定編碼和選項
try (BufferedWriter writer = Files.newBufferedWriter(
        Paths.get("file.txt"), 
        StandardCharsets.UTF_8,
        StandardOpenOption.APPEND)) {
    writer.write("Content");
}

使用 FileOutputStream

寫入二進位檔案:

import java.io.FileOutputStream;

// 寫入位元組
try (FileOutputStream fos = new FileOutputStream("file.bin")) {
    byte[] data = {0x48, 0x65, 0x6C, 0x6C, 0x6F};
    fos.write(data);
}

// 附加模式
try (FileOutputStream fos = new FileOutputStream("file.bin", true)) {
    fos.write(data);
}

使用 PrintWriter

方便格式化輸出:

import java.io.PrintWriter;

try (PrintWriter writer = new PrintWriter("file.txt")) {
    writer.println("Line 1");
    writer.println("Line 2");
    writer.printf("Name: %s, Age: %d%n", "Alice", 25);
    writer.print("No newline");
}

// 指定編碼
try (PrintWriter writer = new PrintWriter("file.txt", StandardCharsets.UTF_8)) {
    writer.println("UTF-8 content");
}

// 自動 flush
try (PrintWriter writer = new PrintWriter(
        new BufferedWriter(new FileWriter("file.txt")), true)) {
    writer.println("Auto-flushed");
}

使用 FileWriter

簡單的字元寫入:

import java.io.FileWriter;

try (FileWriter writer = new FileWriter("file.txt")) {
    writer.write("Hello, World!");
    writer.write('\n');
    writer.write("Second line");
}

// 附加模式
try (FileWriter writer = new FileWriter("file.txt", true)) {
    writer.write("Appended");
}

// 指定編碼(Java 11+)
try (FileWriter writer = new FileWriter("file.txt", StandardCharsets.UTF_8)) {
    writer.write("UTF-8 content");
}

寫入選項

StandardOpenOption 常用選項:

選項說明
CREATE不存在則建立
CREATE_NEW建立新檔案(存在則失敗)
APPEND附加模式
TRUNCATE_EXISTING清空現有內容
WRITE開啟寫入
SYNC同步寫入
// 組合使用
Files.writeString(Paths.get("file.txt"), "Content",
    StandardOpenOption.CREATE,
    StandardOpenOption.WRITE,
    StandardOpenOption.TRUNCATE_EXISTING);

實際範例

寫入日誌

public class SimpleLogger {
    private final Path logFile;
    private final DateTimeFormatter formatter = 
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public SimpleLogger(String filename) {
        this.logFile = Paths.get(filename);
    }

    public void log(String message) {
        String timestamp = LocalDateTime.now().format(formatter);
        String logEntry = String.format("[%s] %s%n", timestamp, message);
        
        try {
            Files.writeString(logFile, logEntry, 
                StandardOpenOption.CREATE, 
                StandardOpenOption.APPEND);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

寫入 CSV

public void writeCSV(String filename, List<String[]> data) throws IOException {
    try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename))) {
        for (String[] row : data) {
            writer.write(String.join(",", row));
            writer.newLine();
        }
    }
}

// 使用
List<String[]> data = Arrays.asList(
    new String[]{"Name", "Age", "City"},
    new String[]{"Alice", "25", "Taipei"},
    new String[]{"Bob", "30", "Kaohsiung"}
);
writeCSV("data.csv", data);

寫入 JSON(使用 Jackson)

public <T> void writeJson(String filename, T object) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    mapper.writerWithDefaultPrettyPrinter()
          .writeValue(new File(filename), object);
}

// 使用
User user = new User("Alice", 25);
writeJson("user.json", user);

複製檔案

public void copyFile(String source, String target) throws IOException {
    try (InputStream in = new FileInputStream(source);
         OutputStream out = new FileOutputStream(target)) {
        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1) {
            out.write(buffer, 0, bytesRead);
        }
    }
}

// 或使用 Files
Files.copy(Paths.get(source), Paths.get(target), 
    StandardCopyOption.REPLACE_EXISTING);

產生報表

public void generateReport(String filename, List<SalesRecord> records) 
        throws IOException {
    try (PrintWriter writer = new PrintWriter(filename)) {
        // 標題
        writer.println("=".repeat(50));
        writer.println("銷售報表");
        writer.println("=".repeat(50));
        writer.println();

        // 表頭
        writer.printf("%-20s %10s %15s%n", "商品", "數量", "金額");
        writer.println("-".repeat(50));

        // 資料
        double total = 0;
        for (SalesRecord r : records) {
            writer.printf("%-20s %10d %15.2f%n", 
                r.getProduct(), r.getQuantity(), r.getAmount());
            total += r.getAmount();
        }

        // 合計
        writer.println("-".repeat(50));
        writer.printf("%-20s %10s %15.2f%n", "合計", "", total);
    }
}

批次寫入

public void batchWrite(String filename, List<String> data, int batchSize) 
        throws IOException {
    try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filename))) {
        int count = 0;
        for (String line : data) {
            writer.write(line);
            writer.newLine();
            count++;
            
            if (count % batchSize == 0) {
                writer.flush();  // 定期 flush
            }
        }
    }
}

方法比較

方法適用場景特點
Files.writeString()小型文字最簡潔
Files.write()位元組或多行彈性大
BufferedWriter大型文字效能好
PrintWriter格式化輸出方便格式化
FileOutputStream二進位位元組寫入

注意事項

處理編碼

// 明確指定編碼
try (BufferedWriter writer = new BufferedWriter(
        new OutputStreamWriter(
            new FileOutputStream("file.txt"), StandardCharsets.UTF_8))) {
    writer.write("中文內容");
}

// 使用 Files(預設 UTF-8)
Files.writeString(Paths.get("file.txt"), "中文內容");

確保寫入完成

// 使用 flush
writer.flush();

// 使用 SYNC 選項
Files.writeString(path, content, StandardOpenOption.SYNC);

原子寫入

// 先寫入暫存檔案,再重新命名
Path temp = Files.createTempFile("temp", ".txt");
Files.writeString(temp, content);
Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE);

重點整理

  • 小檔案:使用 Files.writeString()Files.write()
  • 大檔案:使用 BufferedWriter
  • 格式化輸出:使用 PrintWriter
  • 二進位:使用 FileOutputStream
  • 一定要使用 try-with-resources 關閉資源
  • 明確指定編碼避免問題
  • 考慮使用原子寫入確保資料完整性