1. Java 如何學會處理檔案
Java 與檔案:一切從何開始
很久以前,在遙遠的 1996 年,語言的開發者提出了第一種處理檔案的方式 — 套件 java.io。它引入了 File、FileInputStream、FileOutputStream、Reader、Writer 等類別。這些類別讓我們可以進行各種檔案操作:判斷檔案是否存在、取得大小、修改時間,並處理目錄。
然而,就像許多第一版一樣,它並不完美 — 有很多事情本可以做得更便利與更安全。
舊 API (java.io) 的限制
- File 不是檔案,而是「捷徑」。 類別 File 並不會讀寫內容;它描述的是路徑與中繼資料。要讀/寫必須使用獨立的串流:FileInputStream/FileOutputStream 或 Reader/Writer。
- 路徑處理令人頭疼。 像 "C:\\Users\\" + user + "\\Documents" 這樣手動拼接路徑,在 Windows 與 Linux 之間移植時常常出錯。
- 不支援符號連結。 舊 API 不理解符號連結(symlink),可能錯誤地與之互動。
- 對檔案屬性的支援薄弱. 不易取得存取權限、擁有者、隱藏等旗標等資訊。
- 對大型檔案效率不佳。 傳統串流對大量資料不夠便利與快速,且缺乏非同步 I/O。
java.nio.file 的出現(NIO.2,Java 7)
在 Java 7 出現了新套件:java.nio.file(又稱 NIO.2)。它帶來了:
- Path — 現代的路徑抽象(包含 ZIP、雲端與網路檔案系統中的路徑)。
- Files — 用於讀寫、複製、刪除的靜態工具方法。
- FileSystem — 不僅能操作本機磁碟,還能處理其他檔案系統。
- 支援符號連結、延伸屬性與存取權限。
- 非同步 I/O 與在大量資料上的更佳效能.
- 更方便地操作目錄與串流式 API。
NIO 是「New Input/Output」的縮寫;即使已經問世十多年,對 Java 而言它仍是現代的標準。
2. 做法比較: java.io vs java.nio.file
File 類(java.io)
- 表示檔案或目錄的路徑與其中繼資料。
- 不會讀/寫檔案內容。
- 可取得是否存在、大小、型別(檔案/目錄)、絕對路徑等資訊。
import java.io.File;
public class ExampleIO {
public static void main(String[] args) {
File file = new File("example.txt");
if (file.exists()) {
System.out.println("檔案存在!");
System.out.println("大小:" + file.length() + " 位元組");
}
}
}
Path 類(java.nio.file)
- 現代的路徑抽象,能自然地組合與正規化。
- 可表示本機、封存(壓縮檔)以及網路路徑。
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ExampleNIOPath {
public static void main(String[] args) throws Exception {
Path path = Paths.get("example.txt");
if (Files.exists(path)) {
System.out.println("找到檔案!");
System.out.println("大小:" + Files.size(path) + " 位元組");
}
}
}
Files 類(java.nio.file)
- 一組用於整體或分段讀/寫的靜態方法。
- 檔案的複製、刪除與移動。
- 建立/刪除目錄。
- 存取擴充屬性。
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class ExampleNIOFiles {
public static void main(String[] args) throws Exception {
Path path = Paths.get("example.txt");
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
System.out.println(line);
}
}
}
比較表
| 功能 | |
|
|---|---|---|
| 路徑表示 | 是 | 是 |
| 讀寫內容 | 否 | 是(經由 Files) |
| 路徑操作 | 不方便 | 方便(resolve, normalize) |
| 符號連結 | 否 | 是 |
| 檔案屬性 | 受限 | 擴充 |
| 非同步 I/O | 否 | 是(NIO.2) |
| 跨平台性 | 部分 | 極佳 |
3. 何時該用哪一個?
舊 API (java.io):什麼時候需要?
- 為了支援 legacy 程式碼。 若專案始於 Java 7 之前,且各處都使用 File、FileInputStream、FileOutputStream,可能就得維持相容性。
- 在新專案中 — 不建議。 今天還使用「老前輩」File,就好比用 VHS 錄影帶看影片。
新 API (java.nio.file):現代標準
- 在新專案中一律優先選用 Path 與 Files。
- 更簡潔、安全、強大,且更容易與串流、lambda 與 try-with-resources 整合。
速查備忘
- 讀取、寫入、複製、刪除? — 透過 Files。
- 路徑操作(合併、正規化)? — 透過 Path。
- 大小、日期、存取權限? — Files.getAttribute() 與相關方法。
- 遍歷目錄(遞迴)? — Files.walk、Files.list。
4. 範例:舊/新 API 的程式碼長什麼樣
範例 1: 檢查檔案是否存在
舊方法:
import java.io.File;
File file = new File("data.txt");
if (file.exists()) {
System.out.println("找到檔案!");
}
新方法:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get("data.txt");
if (Files.exists(path)) {
System.out.println("找到檔案!");
}
範例 2: 讀取檔案的所有行
舊方法:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
File file = new File("data.txt");
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
新方法:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
Path path = Paths.get("data.txt");
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
System.out.println(line);
}
說明: 新方法更短且更安全;使用 try-with-resources 後,資源管理更為簡單。
範例 3: 將字串寫入檔案
舊方法:
import java.io.FileWriter;
FileWriter writer = new FileWriter("output.txt");
writer.write("哈囉,檔案!");
writer.close();
新方法:
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get("output.txt");
Files.write(path, "哈囉,檔案!".getBytes());
5. 實用細節
為什麼 Java 要徹底重做?
- 安全與可靠性。 更少資源關閉與路徑處理上的錯誤。
- 跨平台性。 對 Windows、Linux 甚至 ZIP 封存提供一致的類別。
- 易於擴充。 更容易加入雲端與網路檔案系統的支援。
- 效能。 在大型檔案與非同步操作上表現更佳。
- 與現代 Java 生態相容。 Lambda、Stream、try-with-resources。
如何從舊 API 過渡到新 API?
幾乎隨時都能把 File 轉成 Path,反之亦然:
File file = new File("data.txt");
Path path = file.toPath();
File file2 = path.toFile();
遇到舊程式碼該怎麼辦?
- 別慌:一切都可以做得更簡單、更現代。
- 若有餘裕 — 改寫為 java.nio.file。
- 若無法立即改寫 — 使用橋接(toPath()、toFile())並逐步遷移。
6. 最後一個範例:迷你應用程式
範例: 檢查檔案是否存在,並印出其大小。
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class FileInfo {
public static void main(String[] args) {
Path path = Paths.get("test.txt");
if (Files.exists(path)) {
try {
long size = Files.size(path);
System.out.println("找到檔案。大小:" + size + " 位元組");
} catch (Exception e) {
System.out.println("取得檔案大小時發生錯誤:" + e.getMessage());
}
} else {
System.out.println("找不到檔案!");
}
}
}
我們在此使用了:
- Path — 路徑表示。
- Files.exists() — 檢查是否存在。
- Files.size() — 取得大小。
7. 使用檔案 API 時的常見錯誤
錯誤 №1: 以為 File 能夠讀寫內容。 其實 File 只是「捷徑」。要讀/寫請使用 FileInputStream/FileOutputStream(舊 API)或 Files 的工具方法(新 API)。
錯誤 №2: 用 + 或斜線手動拼接路徑。 這在不同作業系統上會造成錯誤。請使用 Path.resolve(),或以多個參數呼叫 Paths.get(...)。
錯誤 №3: 忘記關閉串流。 在舊 API 中,這是資源外洩的常見原因。在新 API 中,許多透過 Files 的操作不會建立串流;若需要,請使用 try-with-resources。
錯誤 №4: 在新專案仍使用舊 API。 若要啟動新專案 — 請選擇 java.nio.file。只有在需要相容於 legacy 程式碼時,才有理由回到 java.io。
GO TO FULL VERSION