直接操作檔案或網路流讀寫單一位元組極度低效。BufferedInputStream / BufferedOutputStream 透過內部緩衝區陣列(預設 8KB),將「多次小量讀寫」合併為「少量大量讀寫」,大幅減少系統呼叫次數,是 Java I/O 效能優化的基石。本文將系統整理兩大緩衝流的核心 API、關鍵特性(mark/reset、flush 機制),並附上可直接執行的完整測試程式碼。
FilterInputStream,裝飾者模式包裝底層 InputStreambyte[] buf 緩衝區,支援 mark(int readlimit) / reset() 標記回滾new BufferedInputStream(in, 512)| 方法 | 說明 |
|---|---|
read() | 讀取單一位元組,自動從緩衝區取值,緩衝區耗盡時自動填充 |
read(byte[] b, int off, int len) | 批次讀取至陣列,效能最佳 |
available() | 返回可不阻塞讀取的位元組數(含緩衝區剩餘) |
mark(int readlimit) | 標記當前位置,readlimit 為允許讀取上限前仍能 reset |
reset() | 回到 mark 位置,配合 markSupported() 檢查支援性 |
skip(long n) | 跳過 n 個位元組,內部優化可直接移動指標 |
import java.io.*; public class BufferedInputStreamDemo { private static final int LEN = 5; public static void main(String[] args) { test(); } private static void test() { try (BufferedInputStream bis = new BufferedInputStream( new FileInputStream("bufferedinputstream.txt"), 512)) { // 1. 讀前 5 字節:a b c d e (0x61~0x65) for (int i = 0; i < LEN; i++) { if (bis.available() >= 0) { int b = bis.read(); System.out.printf("%d : 0x%s%n", i, Integer.toHexString(b)); } } // 2. 標記第 6 字節位置 (f) bis.mark(1024); // 3. 跳過 22 字節,指標來到 '0' bis.skip(22); // 4. 讀 5 字節 → "01234" byte[] buf = new byte[LEN]; bis.read(buf, 0, LEN); System.out.println("str1=" + new String(buf)); // 5. 重置回 mark 位置 (f) bis.reset(); // 6. 再讀 5 字節 → "fghij" bis.read(buf, 0, LEN); System.out.println("str2=" + new String(buf)); } catch (IOException e) { e.printStackTrace(); } } } abcdefghijklmnopqrstuvwxyz 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ 0 : 0x61 1 : 0x62 2 : 0x63 3 : 0x64 4 : 0x65 str1=01234 str2=fghij FilterOutputStream,包裝底層 OutputStreamflush() / close() 才真正寫入底層new BufferedOutputStream(out, 16)| 方法 | 說明 |
|---|---|
write(int b) | 寫入單一位元組到緩衝區 |
write(byte[] b, int off, int len) | 批次寫入,效能最佳 |
flush() | 強制將緩衝區內容推送到底層流 |
close() | 自動 flush 後關閉底層流 |
測試程式碼核心邏輯:建立 16 字節緩衝區,寫入 10/20 字節,觀察檔案實際落盤時機。
public class BufferedOutputStreamDemo { private static final byte[] LETTERS = "abcdefghijklmnopqrstuvwxyz".getBytes(); public static void main(String[] args) { test(10, false); // 場景 1 test(10, true); // 場景 2 + flush test(20, false); // 場景 3 寫入超過緩衝區 } private static void test(int writeCount, boolean manualFlush) { File file = new File("out.txt"); if (file.exists()) file.delete(); try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file), 16)) { bos.write(LETTERS, 0, writeCount); bos.write('\n'); if (manualFlush) bos.flush(); // 手動 flush // 此處模擬使用者輸入等待,觀察檔案內容 Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } } } | 場景 | 寫入量 | 手動 flush | 等待期間檔案內容 | close 後檔案內容 | 原理解析 |
|---|---|---|---|---|---|
| 1 | 10 字節 | 否 | 空 | abcdefghij\n | 未滿緩衝區(16),資料留在記憶體,close 時才 flush |
| 2 | 10 字節 | 是 | abcdefghij\n | abcdefghij\n | flush() 強制推送,資料即時落盤 |
| 3 | 20 字節 | 否 | abcdefghijklmnopqrst | abcdefghijklmnopqrst\n | 超過緩衝區直接繞過緩衝寫入底層,close 再寫入換行 |
read(byte[]) / write(byte[]),避免單位元組操作。flush()。BufferedInputStream 賦予「標記回滾」隨機存取能力,BufferedOutputStream 提供「批次寫入」吞吐優勢。掌握緩衝區滿溢、flush、close 三者觸發落盤的精確時機,是寫出高效、正確 I/O 程式的關鍵。建議在專案中封裝通用的 IOUtils.copyLarge(in, out, bufferSize) 工具方法,統一緩衝策略,避免重複造輪子。