Java IO 體系龐雜,初學者易迷失在眾多類名中。本文以「流的導向」、「裝飾者模式增強」、「隨機訪問」三條主線,系統梳理核心類結構與實戰組合模式,助你建立清晰的 IO 知識地圖。
Java IO 按處理單位劃分為 字節流(Byte Stream) 與 字符流(Char Stream),分別對應 8-bit 字節與 16-bit Unicode 字符。
| 方向 | 節點流(直接操作目標) | 用途 |
|---|---|---|
| 輸入 | ByteArrayInputStream | 內存緩衝區作為輸入源 |
| FileInputStream | 文件讀取 | |
| PipedInputStream | 線程間管道通信 | |
| SequenceInputStream | 合併多個輸入流 | |
| StringBufferInputStream (已棄用) | 字符串轉輸入流 | |
| 輸出 | ByteArrayOutputStream | 寫入內存緩衝區 |
| FileOutputStream | 文件寫入 | |
| PipedOutputStream | 線程間管道通信 | |
| SequenceOutputStream | 合併多個輸出流 |
字符流類名以 Reader/Writer 結尾,功能與字節流對應,差別在於讀寫單位為 char,內部自動處理編碼轉換。
CharArrayReader ↔ ByteArrayInputStreamStringReader ↔ StringBufferInputStreamFileReader ↔ FileInputStreamPipedReader ↔ PipedInputStreamCharArrayWriter ↔ ByteArrayOutputStreamStringWriter(無字節對應)FileWriter ↔ FileOutputStreamPipedWriter ↔ PipedOutputStreamInputStreamReader:字節輸入流 → 字符輸入流(指定 Charset)OutputStreamWriter:字節輸出流 → 字符輸出流(指定 Charset)最佳實踐: 涉及文本文件讀寫,優先使用 FileReader/FileWriter 或顯式指定編碼的轉換流,避免平台默認編碼差異導致亂碼。
Java IO 大量使用 裝飾者模式,通過 FilterInputStream/FilterOutputStream 及其子類,在不修改原流類的前提下,動態組合功能:緩衝、格式化、基本類型讀寫、行號記錄等。
| 功能 | 字節流裝飾 | 字符流裝飾 |
|---|---|---|
| 緩衝(提升效率) | BufferedInputStream | BufferedReader |
| 基本類型讀寫 | DataInputStream / DataOutputStream | —(改用 BufferedReader.readLine) |
| 行號記錄 | LineNumberInputStream (棄用) | LineNumberReader |
| 回退讀取 | PushbackInputStream | PushbackReader |
| 格式化輸出 | PrintStream | PrintWriter |
// 高效讀取文本文件(緩衝 + 字符流 + 指定編碼)
try (BufferedReader in = new BufferedReader(
new InputStreamReader(new FileInputStream("data.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = in.readLine()) != null) { /* process */ }
}
// 高效寫入二進制數據(緩衝 + 基本類型)
try (DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("data.bin")))) {
out.writeDouble(3.14159);
out.writeUTF("Hello World");
}
RandomAccessFile 獨立於流體系,同時實現 DataInput/DataOutput 接口,支持:
"r"(只讀)、"rw"(讀寫)、"rws"/"rwd"(同步寫入)seek(long pos) 定位文件指針getFilePointer() 獲取當前位置length() 獲取文件長度適用場景:大文件斷點續傳、稀疏文件處理、需要隨機修改固定長度記錄的數據文件。
以下代碼演示了六大典型 IO 操作場景:
import java.io.*;
public class IODemo {
public static void main(String[] args) throws IOException {
// 1. 緩衝按行讀取文件
StringBuilder content = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader("TestIO.java"))) {
String line;
while ((line = br.readLine()) != null) content.append(line).append("\n");
}
// 2. 從鍵盤讀取一行(標準輸入轉字符流再緩衝)
try (BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in))) {
System.out.println("輸入一行: " + stdin.readLine());
}
// 3. 從字符串讀取(StringReader)
try (StringReader sr = new StringReader(content.toString())) {
int c;
while ((c = sr.read()) != -1) System.out.print((char) c);
}
// 4. 內存格式化輸出(ByteArray + DataInput)
try (DataInputStream dis = new DataInputStream(
new ByteArrayInputStream(content.toString().getBytes()))) {
while (true) System.out.print((char) dis.readByte());
} catch (EOFException ignored) { System.out.println("\nEnd of stream"); }
// 5. 輸出到文件(緩衝 + 格式化 + 行號)
try (BufferedReader br = new BufferedReader(new StringReader(content.toString()));
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("TestIO.out")))) {
int lineNo = 1;
String line;
while ((line = br.readLine()) != null) pw.println(lineNo++ + ":" + line);
}
// 6. 數據存儲與恢復(DataOutput/Input + 緩衝)
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("Data.txt")))) {
dos.writeDouble(Math.PI);
dos.writeChars(" Thas was pi:writeChars ");
dos.writeBytes("Thas was pi:writeByte ");
}
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("Data.txt")));
BufferedReader br = new BufferedReader(new InputStreamReader(dis))) {
System.out.println(dis.readDouble());
System.out.println(br.readLine());
System.out.println(br.readLine());
} catch (EOFException ignored) { System.out.println("End of stream"); }
// 7. RandomAccessFile 隨機讀寫
try (RandomAccessFile rf = new RandomAccessFile("rtest.dat", "rw")) {
for (int i = 0; i < 10; i++) rf.writeDouble(i * 1.414);
}
try (RandomAccessFile rf = new RandomAccessFile("rtest.dat", "r")) {
for (int i = 0; i < 10; i++) System.out.println("Value " + i + ":" + rf.readDouble());
}
try (RandomAccessFile rf = new RandomAccessFile("rtest.dat", "rw")) {
rf.seek(5 * 8); // 定位第 6 個 double
rf.writeDouble(47.0001);
}
try (RandomAccessFile rf = new RandomAccessFile("rtest.dat", "r")) {
for (int i = 0; i < 10; i++) System.out.println("Value " + i + ":" + rf.readDouble());
}
}
}
FilterInputStream 及子類動態組合功能,避免繼承爆炸。InputStreamReader/OutputStreamWriter 適配字節流與字符流接口差異。Files.newBufferedReader() (NIO.2) 簡化流創建。掌握 Java IO 的關鍵在於理解「流的導向決定處理單位,裝飾者決定功能組合」。實戰中優先使用 try-with-resources 確保資源釋放,文本處理首選字符流 + 顯式編碼,二進制處理首選字節流 + 緩衝,隨機訪問選 RandomAccessFile 或 NIO FileChannel。內化這套邏輯,IO 代碼將不再是「管道連接」的試錯遊戲,而是精準可控的工程藝術。