JDK 大量運用設計模式保證類庫可擴展性,java.io 包是裝飾器模式的教科書級應用。本文通過源碼級分析 OutputStream 體系,揭示 FilterOutputStream 如何作為抽象裝飾器,讓 BufferedOutputStream、DataOutputStream 等具體裝飾器動態組合功能,並指導你實現自定義輸出流。
OutputStream)FileOutputStream、ByteArrayOutputStream)FilterOutputStream)BufferedOutputStream、DataOutputStream)
public abstract class OutputStream implements Closeable, Flushable {
public abstract void write(int b) throws IOException; // 核心寫入契約
public void write(byte[] b) throws IOException { ... } // 默認委託單字節寫入
public void write(byte[] b, int off, int len) throws IOException { ... }
public void flush() throws IOException { }
public void close() throws IOException { }
}
FileOutputStream:寫入文件;ByteArrayOutputStream:寫入內存字節數組(自動擴容);PipedOutputStream:線程間管道輸出。它們直接繼承 OutputStream,實現 write(int b) 完成實際輸出。
public class FilterOutputStream extends OutputStream {
protected final OutputStream out; // 持有被裝飾對象引用
protected FilterOutputStream(OutputStream out) {
this.out = out;
}
// 默認轉發實現:無增強,單純透傳
public void write(int b) throws IOException {
out.write(b);
}
public void write(byte[] b) throws IOException {
out.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
public void flush() throws IOException { out.flush(); }
public void close() throws IOException { out.close(); }
// ... 其他方法均轉發
}
關鍵設計:構造器強制傳入 OutputStream,編譯期保證被裝飾對象存在;所有核心方法默認轉發,**子類只需覆寫需增強的方法**,遵循開閉原則。
BufferedOutputStream:緩衝寫入增強
public class BufferedOutputStream extends FilterOutputStream {
private byte[] buf;
private int count;
public BufferedOutputStream(OutputStream out) {
this(out, 8192); // 默認 8KB 緩衝區
}
@Override
public synchronized void write(int b) throws IOException {
if (count >= buf.length) flushBuffer(); // 滿則刷新
buf[count++] = (byte) b;
}
@Override
public synchronized void write(byte[] b, int off, int len) throws IOException {
if (len >= buf.length) { // 大數據直接寫入底層,避免雙重拷貝
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) flushBuffer();
System.arraycopy(b, off, buf, count, len);
count += len;
}
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
// flush/close 時調用 flushBuffer()
}
DataOutputStream:基本類型序列化增強
public class DataOutputStream extends FilterOutputStream implements DataOutput {
public DataOutputStream(OutputStream out) { super(out); }
@Override public void writeInt(int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write(v & 0xFF);
}
// writeLong, writeDouble, writeUTF 等類似
}
寫入數據時自動過濾空格字符(' '),如 "java io" → "javaio"。
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class SkipSpaceOutputStream extends FilterOutputStream {
public SkipSpaceOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int b) throws IOException {
if (b != ' ') { // 過濾空格
super.write(b); // 轉發給被裝飾流
}
}
// 為性能考慮,亦可覆寫 write(byte[], int, int) 批量過濾
@Override
public void write(byte[] b, int off, int len) throws IOException {
for (int i = off; i < off + len; i++) {
if (b[i] != ' ') {
out.write(b[i]);
}
}
}
}
public class TestSkipSpace {
public static void main(String[] args) throws IOException {
byte[] input = "a b c d e f".getBytes();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(baos));
out.write(input);
out.close();
System.out.println(baos.toString()); // 輸出: abcdef
}
}
// 鏈式裝飾:文件 → 緩衝 → 數據類型序列化 → 空格過濾
OutputStream out = new SkipSpaceOutputStream(
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("test.txt"))));
out.write("h e l l o".getBytes()); // 文件實際寫入: hello
out.close();
每層裝飾器只關注單一職責,組合靈活,運行時動態決定功能疊加順序。
| 輸出體系 | 輸入體系 | 字符體系(輸出) | 字符體系(輸入) |
|---|---|---|---|
| OutputStream | InputStream | Writer | Reader |
| FilterOutputStream | FilterInputStream | FilterWriter | FilterReader |
| BufferedOutputStream | BufferedInputStream | BufferedWriter | BufferedReader |
| DataOutputStream | DataInputStream | PrintWriter | - |
| PrintStream | - | - | - |
| ObjectOutputStream | ObjectInputStream | - | - |
| GZIPOutputStream | GZIPInputStream | - | - |
| CipherOutputStream | CipherInputStream | - | - |
所有體系均遵循:抽象基類 → 具體基礎流 → 抽象裝飾器 → 具體功能裝飾器 結構。
| 模式 | 適用場景 | IO 體系例子 |
|---|---|---|
| 裝飾器 | 動態組合單一職責功能,運行時決定行為 | Buffered + DataOutput + GZIP 任意組合 |
| 繼承 | 靜態擴展,子類化行為固定 | FileOutputStream 繼承 OutputStream |
| 適配器 | 介面不兼容轉換 | OutputStreamWriter (字節流→字符流) |
| 代理 | 控制訪問、延遲初始化、遠程調用 | - |
FilterOutputStream 以組合替代繼承,構造器注入被裝飾對象,默認轉發,子類選擇性增強 —— 裝飾器模式標準落地。Buffered → GZIP → File 先緩衡再壓縮寫磁盤;GZIP → Buffered → File 壓縮後緩衡(通常無意義)。FilterOutputStream/FilterInputStream/FilterWriter/FilterReader,覆寫核心 write/read 方法,即可無縫融入 JDK IO 體系。java.io、java.nio、第三方 IO 庫(Okio、Netty ByteBuf)的設計邏輯。