BufferedReader 是字元導向的緩衝輸入流,繼承自 Reader。它透過內部字元緩衝區(預設 8192 字元)批次從底層流讀取資料,再逐字元或逐行供應用程式消費,大幅減少系統呼叫開銷。其獨特的 readLine() 與 mark()/reset() 機制,使其成為文字檔案處理、配置解析、日誌分析的首選工具。
// 建構子
BufferedReader(Reader in) // 預設 8KB 緩衝
BufferedReader(Reader in, int sz) // 自訂緩衝區大小
// 基礎讀取
int read() // 單字元,-1 表 EOF
int read(char[] cbuf, int off, int len) // 讀入字元陣列
String readLine() // 讀取一行(不含行結尾符)
boolean ready() // 是否可立即讀取而不阻塞
long skip(long n) // 跳過 n 字元
// 標記與重置(支援 markSupported() == true)
void mark(int readAheadLimit) // 標記目前位置,限制往前讀取上限
void reset() // 重置至標記位置
// 生命週期
void close()
public class BufferedReader extends Reader {
private Reader in; // 底層字元流
private char[] cb; // 字元緩衝陣列
private int nChars; // 緩衝區有效字元數
private int nextChar; // 下一個讀取位置
// 標記相關
private int markedChar = UNMARKED; // 標記位置,-1 表無標記
private int readAheadLimit; // 標記允許的最大前讀長度
private boolean skipLF = false; // 是否跳過 \n(處理 \r\n)
private boolean markedSkipLF; // 標記時的 skipLF 狀態
private static final int UNMARKED = -1;
private static final int INVALIDATED = -2;
private static final int defaultCharBufferSize = 8192;
}
fill() 是緩衝區補充的核心,根據標記狀態與前讀限制分為四種情況:
// markedChar <= UNMARKED
dst = 0;
// 直接從底層流讀滿緩衝區
n = in.read(cb, 0, cb.length);
nChars = n; nextChar = 0;
最常見情境:循序讀取,緩衝區用盡後整區替換。
// markedChar > UNMARKED 且 delta >= readAheadLimit
markedChar = INVALIDATED;
readAheadLimit = 0;
dst = 0;
// 同情況 1,捨棄標記重新填充
標記保護範圍被超越,強制失效標記,避免記憶體無限增長。
// delta < readAheadLimit 且 readAheadLimit <= cb.length
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
dst = delta;
nextChar = nChars = delta;
// 續讀新資料至緩衝區剩餘空間
將標記後未讀資料移至緩衝區頭部,續讀新資料接在後面,標記位置歸零。
// delta < readAheadLimit 且 readAheadLimit > cb.length
char[] ncb = new char[readAheadLimit];
System.arraycopy(cb, markedChar, ncb, 0, delta);
cb = ncb;
markedChar = 0;
dst = delta;
nextChar = nChars = delta;
配置更大緩衝區以容納標記保護範圍,代價是記憶體分配與複製開銷。
掃描緩衝區尋找 \n、\r 或 \r\n,處理跨緩衝區邊界的行結尾符,遇到 \r 時透過 skipLF 標記決定是否吞噬後續 \n。這比手工解析更健壯。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class BufferedReaderDemo {
public static void main(String[] args) {
Path path = Path.of("sample.txt");
// 建立測試檔案
try {
Files.writeString(path,
"abcdefghijklmnopqrstuvwxyz\n" +
"0123456789\n" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\n",
StandardCharsets.UTF_8);
} catch (IOException e) { e.printStackTrace(); }
demoMarkReset(path);
demoLineProcessing(path);
}
// 展示 mark/reset 機制
private static void demoMarkReset(Path path) {
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
char[] buf = new char[5];
// 讀前 5 字元:a b c d e
for (int i = 0; i < 5; i++) {
if (br.ready()) System.out.printf("%d : %c%n", i, (char) br.read());
}
// 標記目前位置(f),前讀限制 1024
br.mark(1024);
// 跳過 22 字元,讀 5 字元 -> 01234
br.skip(22);
br.read(buf, 0, 5);
System.out.println("buf=" + new String(buf)); // 01234
// 讀取剩餘行 -> 56789
System.out.println("readLine=" + br.readLine()); // 56789
// 重置至標記位置(f)
br.reset();
// 重新讀取 5 字元 -> fghij
br.read(buf, 0, 5);
System.out.println("buf=" + new String(buf)); // fghij
} catch (IOException e) {
e.printStackTrace();
}
}
// 現代逐行處理範式
private static void demoLineProcessing(Path path) {
try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
int lineNum = 0;
while ((line = br.readLine()) != null) {
System.out.printf("Line %d: %s%n", ++lineNum, line);
// 這裡可進行解析、過濾、轉換等業務邏輯
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
| 參數 | 預設值 | 調優建議 |
|---|---|---|
| 緩衝區大小 | 8192 字元 (16 KB) | 大檔案可增至 64-256 KB;小檔案或記憶體受限環境可降至 1-4 KB |
| readAheadLimit (mark) | 呼叫端指定 | 設定為預期需回溯的最大字元數,過大觸發緩衝區擴充 |
| 預期行長 | 80 字元 | 內部優化參數,極長行(如壓縮 JSON)可考慮自訂實現 |
// Java 8+ 函數式逐行處理,自動資源管理
try (var lines = Files.lines(Path.of("big.log"), StandardCharsets.UTF_8)) {
lines.filter(l -> l.contains("ERROR"))
.map(l -> l.substring(l.indexOf("ERROR")))
.limit(100)
.forEach(System.out::println);
}
Files.lines() 內部使用 BufferedReader,回傳惰性 Stream,適合大檔案過濾轉換,但需注意流關閉時機與檔案鎖定。
reset() 拋出 IOException。read() 消耗字元會影響後續 readLine() 的行邊界判斷。ready() 僅表示「不阻塞」,非 EOF 指標。正確判斷 EOF 應檢查 read() == -1 或 readLine() == null。掌握 BufferedReader 的緩衝策略、標記重置機制與 readLine() 實現細節,能讓你在文字處理、日誌分析、配置解析等場景中遊刃有餘,寫出高效且健壯的 IO 代碼。