1. 逐行讀取檔案:BufferedReader 與同類工具
先前我們已經看過逐位元組讀取:它適合處理二進位格式,但對文字就不太方便。檔案由取決於編碼的字元組成。因此 Java 提供了便利的高階封裝——FileReader、BufferedReader 等類別,能把位元組串流轉成字元與文字行。
想像一個文字檔——可能是程式的運作日誌、使用者清單,甚至是龐大的長篇小說《戰爭與和平》。有時你需要快速把整個檔案讀進來,有時則要逐行走訪,還有時候只想抽出特定的一行。
在 Java 中有多種做法可達成,選擇取決於檔案大小與手頭任務。若需要逐行處理(例如計算行數或尋找某筆紀錄),就用逐行讀取;若檔案不大,也可以整檔載入記憶體,當作行的清單來操作。
為什麼要逐行讀取?
對大型檔案而言,逐行讀取能避免記憶體問題。把數 GB 的日誌整個載入記憶體不是好主意:很可能會遇到 OutOfMemoryError。而逐行讀取即使面對數百 MB 的檔案也能輕鬆應付。
在 Java 中怎麼做?
最經典的方法是使用 BufferedReader(或其近親)以及 readLine() 方法。
import java.io.*;
public class ReadLinesDemo {
public static void main(String[] args) {
String fileName = "example.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
System.out.printf("%3d: %s%n", lineNumber, line);
lineNumber++;
}
} catch (IOException e) {
System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
}
}
}
在這裡我們開啟檔案來讀取,並透過 readLine() 一次讀一行,直到回傳 null 表示到達檔尾。接著將每一行連同行號輸出。
關於 try-with-resources 的重點
你看到的結構 try (...) { ... } 就是 try-with-resources。它保證即使在讀取過程中發生錯誤,檔案也會被關閉。你不需要手動呼叫 close():即使進入 catch/finally,也會自動關閉。
為什麼需要 BufferedReader?
BufferedReader 並非逐字元讀取,而是一次讀取整個區塊(通常為 8192 位元組),這能加速檔案操作。此外,它提供便利的 readLine() 方法,可直接回傳到換行字元為止的一整行。
緩衝區該選多大?
一般而言,BufferedReader 使用大小為 8192 位元組(8 KB)的緩衝區——對多數情境已綽綽有餘。若你會讀到非常長的行(例如每行 100_000 個字元),可以把緩衝區加大:
BufferedReader reader = new BufferedReader(new FileReader(fileName), 65536); // 64 KB 緩衝區
但對一般需求,預設大小已經很適合。
2. 讀取整個檔案:Files.readAllLines 與 Files.readString
如果檔案不大(例如約 10–20 MB),把它整個載入會很方便。比如要快速取得所有行、分析文字,或透過網路傳送。
較新的作法: Files.readAllLines
從 Java 7 起有了好用的 Files 類別,裡面包含許多實用方法。
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class ReadAllLinesDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
List<String> lines = Files.readAllLines(path);
for (int i = 0; i < lines.size(); i++) {
System.out.printf("%3d: %s%n", i + 1, lines.get(i));
}
} catch (IOException e) {
System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
}
}
}
這裡 Files.readAllLines(path) 會回傳行的清單(List<String>)。你可以像操作一般集合一樣:搜尋、排序、篩選。
更進一步: Files.readString (Java 11+)
如果你需要把整個檔案一次取得為單一字串(例如用來搜尋子字串或傳成 JSON),使用 Files.readString:
import java.nio.file.*;
import java.io.IOException;
public class ReadStringDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
try {
String content = Files.readString(path);
System.out.println("檔案內容:");
System.out.println(content);
} catch (IOException e) {
System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
}
}
}
那編碼怎麼辦?
預設情況下,Files.readAllLines 與 Files.readString 會使用系統的預設編碼。若檔案使用了其他編碼(例如 Windows-1251),請明確指定:
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
String content = Files.readString(path, StandardCharsets.UTF_8);
3. 方法比較: 何時用哪一種
| 方法 | 適用時機 | 優點 | 缺點 |
|---|---|---|---|
|
大型檔案、逐行處理 | 省記憶體、彈性高 | 只能逐行讀取 |
|
小到中型檔案 | 直接得到行清單,簡單 | 對大型檔案可能導致 OutOfMemory |
|
小型檔案,需要整段文字 | 一次取得全部內容 | 不會自動依行分割 |
建議:
— 若檔案不大,使用 Files.readAllLines 或 Files.readString。
— 若檔案很大或你不確定大小,使用 BufferedReader.readLine()。
4. 實務任務: 範例與解析
範例 1. 大型檔案的行數統計
假設需要知道一個超大的檔案(例如伺服器日誌)共有多少行。
import java.io.*;
public class LineCount {
public static void main(String[] args) {
String fileName = "biglog.txt";
int lineCount = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
while (reader.readLine() != null) {
lineCount++;
}
System.out.println("檔案中的總行數:" + lineCount);
} catch (IOException e) {
System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
}
}
}
範例 2. 依內容搜尋行
尋找所有出現「錯誤」一詞的行(不分大小寫):
import java.io.*;
public class FindErrorLines {
public static void main(String[] args) {
String fileName = "biglog.txt";
String keyword = "錯誤";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
int lineNumber = 1;
while ((line = reader.readLine()) != null) {
if (line.toLowerCase().contains(keyword)) {
System.out.printf("%3d: %s%n", lineNumber, line);
}
lineNumber++;
}
} catch (IOException e) {
System.out.println("讀取檔案時發生錯誤:" + e.getMessage());
}
}
}
範例 3. 從小型檔案載入設定
檔案 config.txt:
host=localhost
port=8080
mode=dev
整檔讀取並解析為鍵值配對:
import java.nio.file.*;
import java.util.*;
public class ConfigLoader {
public static void main(String[] args) throws Exception {
Path path = Path.of("config.txt");
List<String> lines = Files.readAllLines(path);
Map<String, String> config = new HashMap<>();
for (String line : lines) {
if (line.trim().isEmpty() || line.startsWith("#")) continue; // 略過空行與註解
String[] parts = line.split("=", 2);
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
}
}
System.out.println("已載入的設定:" + config);
}
}
5. 讀取文字檔的常見錯誤
錯誤 №1: 把二進位檔當成文字檔來讀。 如果你用 BufferedReader 或 Files.readAllLines 來開影像或壓縮檔,只會得到亂碼,還存在 OutOfMemoryError 的風險。處理二進位檔請使用 InputStream!
錯誤 №2: 未處理例外。 檔案可能會被刪除、搬移、鎖定。務必用 try-catch 包住讀取並告知使用者問題。
錯誤 №3: 忽略編碼。 如果你的文字是俄文,而你未指定編碼,可能會看到「?????」。請使用 StandardCharsets.UTF_8 或你需要的編碼。
錯誤 №4: 忘了關閉串流。 若不關閉檔案,它可能會一直被鎖到程式結束。務必使用 try-with-resources。
錯誤 №5: 對文字檔用 read() 代替 readLine()。 方法 read() 是逐字元讀取——對處理文字行既慢又不便。讀文字請用 readLine() 或 Files 類別的方法。
GO TO FULL VERSION