前言
Java IO 流是文件、網絡、內存數據交換的核心抽象。本文系統梳理流分類體系、四大抽象基類、節點流與處理流組合模式、常用流實戰代碼,助你構建清晰的 IO 知識圖譜。
一、流分類體系總覽
三個維度分類
| 分類維度 | 類型 | 特點 | 典型類 |
| 按方向 | 輸入流 | 數據流入程序 (read) | InputStream, Reader |
| 輸出流 | 數據流出程序 (write) | OutputStream, Writer |
| 按數據單元 | 字節流 | 8-bit,處理二進制數據 | InputStream/OutputStream 子類 |
| 字符流 | 16-bit Unicode,處理文本 | Reader/Writer 子類 |
| 按功能角色 | 節點流 | 直接操作數據源/目的地 | FileInputStream, FileReader |
| 處理流 | 包裝其他流,提供增強功能 | BufferedInputStream, InputStreamReader |
四大抽象基類
java.lang.Object
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
InputStream OutputStream Reader ← Writer
(字節輸入) (字節輸出) (字符輸入) (字符輸出)
│ │ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴────┐ ┌──┴────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
File... Buffer... File... Buffer... File... Buffer... Print...
二、節點流:直連數據源
| 數據源類型 | 字節流 | 字符流 |
| 文件 | FileInputStream / FileOutputStream | FileReader / FileWriter |
| 內存數組 | ByteArrayInputStream / ByteArrayOutputStream | CharArrayReader / CharArrayWriter |
| 字符串 | - | StringReader / StringWriter |
| 管道 | PipedInputStream / PipedOutputStream | PipedReader / PipedWriter |
文件複製基礎示例
public void copyFileBasic(String src, String dest) throws IOException {
try (FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} // try-with-resources 自動關閉
}
三、處理流:裝飾者模式賦能
核心處理流分類
| 功能 | 字節流 | 字符流 | 核心作用 |
| 緩衝 | BufferedInputStream/OutputStream | BufferedReader/Writer | 減少系統調用,提升性能 |
| 轉換 | - | InputStreamReader/OutputStreamWriter | 字節↔字符,指定編碼 |
| 數據類型 | DataInputStream/OutputStream | - | 基本類型序列化 |
| 對象序列化 | ObjectInputStream/OutputStream | - | 對象圖持久化 |
| 打印 | PrintStream | PrintWriter | 格式化輸出、自動 flush |
| 推回 | PushbackInputStream | PushbackReader | 預讀一字節/字符 |
四、實戰核心場景代碼
1. 緩衝流:性能加速器
public void bufferedCopy(String src, String dest) throws IOException {
// 字節緩衝:適用於二進制文件(圖片、視頻)
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buf = new byte[8192];
int len;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
bos.flush(); // 關鍵:強制刷新緩衝區
}
// 字符緩衝:適用於文本文件,支持按行讀寫
try (BufferedReader br = new BufferedReader(new FileReader(src));
BufferedWriter bw = new BufferedWriter(new FileWriter(dest))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); // 跨平台換行符
}
bw.flush();
}
}
2. 轉換流:編碼橋樑
public void encodingDemo() throws IOException {
// 寫入指定編碼
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("utf8.txt"), StandardCharsets.UTF_8)) {
osw.write("中文測試");
}
// 讀取指定編碼
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("gbk.txt"), "GBK")) {
char[] buf = new char[1024];
int len = isr.read(buf);
System.out.println(new String(buf, 0, len));
}
// 系統默認編碼查看
System.out.println("default charset: " + Charset.defaultCharset());
}
3. 數據流:基本類型二進制存儲
public void dataStreamDemo() throws IOException {
// 寫入
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("data.bin")))) {
dos.writeInt(12345);
dos.writeDouble(3.14159);
dos.writeBoolean(true);
dos.writeUTF("Hello World"); // 修改 UTF-8 字符串
dos.flush();
}
// 讀取:順序必須與寫入一致!
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("data.bin")))) {
int i = dis.readInt(); // 12345
double d = dis.readDouble(); // 3.14159
boolean b = dis.readBoolean(); // true
String s = dis.readUTF(); // "Hello World"
}
}
4. 打印流:便捷輸出
public void printStreamDemo() throws IOException {
// 重定向 System.out 到文件
try (PrintStream ps = new PrintStream(
new FileOutputStream("log.txt"), true, StandardCharsets.UTF_8)) {
System.setOut(ps);
System.out.println("這行輸出到文件了");
System.out.printf("格式化: %d, %.2f%n", 42, 3.14);
// 自動 flush (第二個參數 true)
}
// PrintWriter 更靈活
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(new FileOutputStream("log2.txt"), "UTF-8"), true)) {
pw.println("PrintWriter 也支援自動 flush");
}
}
5. 對象序列化流
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private transient String password; // 不序列化
}
public void objectStreamDemo() throws IOException, ClassNotFoundException {
User user = new User(1, "Alice", "secret123");
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("user.ser")))) {
oos.writeObject(user);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new BufferedInputStream(new FileInputStream("user.ser")))) {
User restored = (User) ois.readObject();
// restored.password 為 null (transient)
}
}
五、流組合模式最佳實踐
標準組合鏈
// 讀取文本文件:節點流 → 緩衝流 → 轉換流(可選)
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("file.txt"), StandardCharsets.UTF_8));
// 寫入二進制文件:節點流 → 緩衝流 → 數據流(可選)
DataOutputStream writer = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("data.bin")));
// 對象序列化:節點流 → 緩衝流 → 對象流
ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(
new FileOutputStream("obj.ser")));
關鍵原則
- 關閉最外層流:會級聯關閉內層流
- try-with-resources:Java 7+ 必須使用,防止洩漏
- 緩衝流必須 flush:寫入完成前數據在內存
- 指定編碼:避免平台默認編碼差異導致亂碼
六、NIO.2 現代化文件操作 (Java 7+)
public void nio2Demo() throws IOException {
Path path = Paths.get("data.txt");
// 一行代碼讀寫
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
Files.write(path, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
// 流式處理大文件
try (Stream<String> stream = Files.lines(path)) {
stream.filter(s -> s.contains("ERROR"))
.forEach(System.out::println);
}
// 文件屬性
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("創建時間: " + attrs.creationTime());
System.out.println("大小: " + attrs.size());
// 目錄遍歷
Files.walk(Paths.get("."))
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
}
七、性能優化清單
| 場景 | 建議 | 原因 |
| 大文件複製 | FileChannel.transferTo/transferFrom | 零拷貝,內核態完成 |
| 頻繁小量讀寫 | Buffered 流 + 8KB-64KB 緩衝區 | 減少系統調用 |
| 文本處理 | BufferedReader.readLine / Files.lines | 按行處理,內存友好 |
| 對象傳輸 | Protobuf/Kryo/Hessian 替代 JDK 序列化 | 體積小、速度快、跨語言 |
| 高併發日誌 | 異步寫入 + 批量刷新 (Logback/Log4j2) | 不阻塞業務線程 |
八、常見坑點
- 忘記 flush:數據停留在緩衝區,文件為空或不全
- 編碼不一致:寫 UTF-8 讀 GBK → 亂碼
- 流未關閉:文件句柄洩漏,導致「Too many open files」
- 序列化版本不兼容:修改類結構未更新 serialVersionUID → InvalidClassException
- 裝飾順序錯誤:DataOutputStream 包裝 BufferedOutputStream 而非相反
總結
Java IO 流採用裝飾者模式實現功能組合。掌握四大基類、節點流與處理流職責分工、標準組合鏈、編碼處理、序列化機制、NIO.2 現代 API,即可應對絕大多數 IO 場景。性能敏感場景優先考慮 NIO 零拷貝、異步 IO 或成熟日誌框架。