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);
表格: 主要寫入模式
| 選項 | 行為 |
|---|---|
| (預設) | 建立檔案或覆寫既有檔案 |
|
在末端附加;若檔案不存在則錯誤 |
|
在末端附加;若檔案不存在則建立 |
|
將檔案截斷為零後寫入新資料 |
|
建立新檔案;若檔案已存在則錯誤 |
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. 何時需要使用串流(FileInputStream、BufferedReader 等)
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:忘記關閉串流。 如果使用串流(BufferedWriter、BufferedReader),別忘了關閉它們(最好使用 try-with-resources),否則檔案可能保持鎖定狀態。
錯誤 5:未處理例外。 任何檔案操作都可能丟出 IOException。若不處理,程式會異常終止。
GO TO FULL VERSION