CodeGym /課程 /JAVA 25 SELF /檔案操作模式:讀取、寫入、附加寫入

檔案操作模式:讀取、寫入、附加寫入

JAVA 25 SELF
等級 35 , 課堂 3
開放

1. 覆寫式寫入

當你操作檔案時,需要理解「寫入檔案」並不總是同一回事。有時需要完全覆寫檔案(例如儲存新的報表),有時則需要在既有檔案末端新增資訊(例如記錄應用程式事件日誌)。在某些情況下,只需要讀取檔案內容而不更動它。

在 Java(特別是現代的 java.nio.file 套件)中,檔案操作模式由一組你傳給寫入方法的特殊選項決定,例如 Files.write()。這些選項能讓你明確指定要覆寫檔案,或在末端附加內容。

運作方式

當你呼叫 Files.write(path, data) 時,Java 會在預設情況下建立新檔案或覆寫已存在的檔案。舊的檔案內容會被清除,只留下新的資料。

這是預設行為——某種程度上等同於對檔案進行「硬重置」。如果檔案原本有 1000 行,而你只寫入一行——先前的一切都會不留痕跡地消失。

範例:將字串寫入檔案

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class OverwriteFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> lines = List.of("哈囉,世界!", "這是檔案中的新內容。");

        // 將行寫入檔案(舊內容將被刪除)
        Files.write(path, lines);

        System.out.println("檔案已成功覆寫!");
    }
}

執行上述程式後,myfile.txt 檔案中只會留下清單 lines 裡的兩行。先前的內容會消失(Java 並不會表現出任何「遺憾」)。

2. 附加(append):在檔案末端新增資料

在某些需求(例如事件日誌、logging、資料累積)中,我們不希望刪除舊內容,而是要在末端新增新行。舊的 API 常用帶有 append = true 旗標的 FileWriter。在現代 API 中,作法更簡潔且明確。

如何實作

只需在 Files.write() 中傳入選項 StandardOpenOption.APPEND

import java.nio.file.*;
import java.io.IOException;
import java.util.List;

public class AppendFileExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");
        List<String> moreLines = List.of("又新增了一行。", "再來一行!");

        // 在檔案末端附加行(舊內容會保留)
        Files.write(path, moreLines, StandardOpenOption.APPEND);

        System.out.println("已在檔案末端新增行!");
    }
}

重要說明: 若檔案不存在,嘗試附加寫入會拋出錯誤——在 APPEND 模式下,Java 不會自動建立檔案。若要在檔案不存在時自動建立,請同時使用兩個選項:

Files.write(path, moreLines, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

實務上的樣貌

  • 以寫入(覆寫)模式執行——檔案中有兩行。
  • 以附加模式執行——會在這些行之後新增新行,舊內容保留。

3. 選項組合: CREATE, APPEND, TRUNCATE_EXISTING

在 Java 中可以組合多個選項,以更靈活地控制開啟檔案的模式:

  • StandardOpenOption.CREATE —— 若檔案不存在則建立。
  • StandardOpenOption.APPEND —— 在末端附加資料。
  • StandardOpenOption.TRUNCATE_EXISTING —— 若檔案存在,將其截斷為 0 位元組(清空)。
  • StandardOpenOption.CREATE_NEW —— 建立新檔案;若已存在則錯誤。

範例:若檔案不存在則建立,並在末端附加資料

Files.write(path, moreLines, StandardOpenOption.CREATE, StandardOpenOption.APPEND);

範例:建立檔案或完全清空後寫入新資料

Files.write(path, lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

表格: 主要寫入模式

選項 行為
(預設) 建立檔案或覆寫既有檔案
APPEND
在末端附加;若檔案不存在則錯誤
APPEND + CREATE
在末端附加;若檔案不存在則建立
TRUNCATE_EXISTING
將檔案截斷為零後寫入新資料
CREATE_NEW
建立新檔案;若檔案已存在則錯誤

4. 讀寫二進位檔案

前面我們都在處理字串。但有時需要讀寫「原始」位元組(例如圖片、壓縮檔、PDF 檔)。此時請使用:

  • Files.readAllBytes(path) —— 將檔案讀入位元組陣列。
  • Files.write(path, byteArray) —— 將位元組陣列寫入檔案。

範例:複製檔案

import java.nio.file.*;
import java.io.IOException;

public class CopyBinaryFileExample {
    public static void main(String[] args) throws IOException {
        Path source = Paths.get("logo.png");
        Path target = Paths.get("logo_copy.png");

        byte[] data = Files.readAllBytes(source);
        Files.write(target, data);

        System.out.println("檔案已複製!");
    }
}

範例:附加二進位資料

byte[] moreData = new byte[] {1, 2, 3, 4, 5};
Files.write(path, moreData, StandardOpenOption.APPEND, StandardOpenOption.CREATE);

注意: 將二進位資料附加到已含有結構化資料(例如圖片)的檔案,通常會導致檔案受損。僅在文字檔或經特殊設計的二進位檔(例如日誌)上使用附加寫入。

5. 何時需要使用串流(FileInputStreamBufferedReader 等)

Files.readAllBytes()Files.write() 適合用於小到中型檔案,能一次把全部內容載入記憶體。若檔案很大(例如數 GB),或想逐行讀取(例如用於日誌分析),請改用串流。

範例:使用 BufferedReader 逐行讀取檔案

import java.nio.file.*;
import java.io.*;

public class ReadLinesExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedReader reader = Files.newBufferedReader(path)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("行:" + line);
            }
        }
    }
}

範例:使用 BufferedWriter 逐行寫入並附加

import java.nio.file.*;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class WriteAppendExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("myfile.txt");

        try (BufferedWriter writer = Files.newBufferedWriter(
                path,
                StandardCharsets.UTF_8,
                StandardOpenOption.CREATE,
                StandardOpenOption.APPEND)) {
            writer.write("再透過 BufferedWriter 寫一行!");
            writer.newLine();
        }
    }
}

6. 實作:建立檔案並向其中附加各行

把上述內容整合成一個應用。假設我們用文字檔維護待辦清單(todo list)。每當使用者新增任務,我們就把它附加到檔案末端。

import java.nio.file.*;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;

public class TodoList {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("todo.txt");
        Scanner scanner = new Scanner(System.in);

        System.out.print("請輸入新任務:");
        String task = scanner.nextLine();

        // 將任務附加到檔案末端(若檔案不存在則建立)
        Files.write(path, 
            List.of(task), 
            StandardOpenOption.CREATE, 
            StandardOpenOption.APPEND);

        System.out.println("任務已新增!");
    }
}

多次執行程式——每個任務都會以新行出現在檔案中。這就是我們的第一個「持久」todo 清單!

7. 例外處理:可能出現哪些問題?

檔案操作總是伴隨錯誤風險。可能發生的情況包括:

  • IOException —— 所有輸入/輸出錯誤的基礎例外。可能出現在檔案被其他程式占用、沒有讀寫權限、磁碟已滿等情況。
  • NoSuchFileException —— 嘗試讀取或附加寫入不存在的檔案(且未指定 CREATE 選項)。
  • FileAlreadyExistsException —— 在使用 CREATE_NEW 選項時,若檔案已存在則發生。

建議: 請一律以 try-catch 包裹檔案操作:

try {
    // 您的檔案操作
} catch (IOException e) {
    System.out.println("處理檔案時發生錯誤: " + e.getMessage());
}

8. 使用檔案開啟模式時的常見錯誤

錯誤 1:在附加寫入時忘了指定 CREATE 選項。 如果你在檔案尚不存在時嘗試附加(APPEND),會得到 NoSuchFileException。若希望自動建立檔案,請務必加入 CREATE

錯誤 2:不小心覆寫了檔案。 呼叫 Files.write(path, data) 而不加任何額外選項,會清除檔案中所有舊內容。若你想新增資料而非刪除舊內容,請使用 APPEND

錯誤 3:嘗試在文字檔中附加二進位資料(或反之)。 若在同一個檔案中混用文字與二進位資料,極可能導致檔案不可讀。請為同一個檔案維持單一格式。

錯誤 4:忘記關閉串流。 如果使用串流(BufferedWriterBufferedReader),別忘了關閉它們(最好使用 try-with-resources),否則檔案可能保持鎖定狀態。

錯誤 5:未處理例外。 任何檔案操作都可能丟出 IOException。若不處理,程式會異常終止。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION