如何掌握【Java】 IO/NIO设计模式?工厂/适配器/装饰器/观察者模式全解析

目录
1.引言
插播一条消息~
2.工厂模式:对象创建的"智能生产线"
2.1定义
2.2UML 图
2.3代码示例:自定义流工厂
2.4生活类比:饮料自动售货机
2.5IO 中的实际应用:File创建流
3.适配器模式:接口转换的"万能转接头"
3.1定义
3.2UML 图
3.3代码示例:字节流到字符流的适配器
3.4生活类比:电源转接头
3.5IO 中的实际应用:字节流与字符流的桥梁
4.装饰器模式:功能增强的"俄罗斯套娃"
4.1定义
4.2UML 图
4.3代码示例:自定义加密装饰流
4.4生活类比:快递包装
4.5IO 中的实际应用:流的多层装饰
5.观察者模式:事件驱动的"订阅-通知"机制
5.1定义
5.2UML 图
5.3代码示例:简易 NIO Selector 实现
5.4生活类比:气象站与显示屏
5.5NIO 中的实际应用:Selector 与事件驱动
6.四种模式对比分析
如何选择合适的模式?
7.总结:设计模式如何塑造 IO/NIO 架构
1.引言
在 Java 开发中,IO/NIO 是我们每天都会接触的基础 API,但你是否思考过:为什么 InputStream 有那么多子类却能统一操作?为什么 BufferedReader 能轻易增强其他流的功能?为什么 NIO 的 Selector 能高效处理成千上万的连接?这些问题的答案,都藏在设计模式的巧妙运用中。
本文将带你深入剖析 IO/NIO 体系中最核心的四种设计模式——工厂模式、适配器模式、装饰器模式和观察者模式。我们不做纯理论讲解,而是结合 JDK 源码实例,用生活化的类比帮你理解模式本质,最终掌握这些设计思想在实际开发中的应用技巧。无论你是想提升源码阅读能力,还是优化自己的项目设计,这篇文章都能给你带来启发。
插播一条消息~
🔍十年经验淬炼 · 系统化AI学习平台推荐
系统化学习AI平台 https://www.captainbed.cn/scy/
https://www.captainbed.cn/scy/
- 📚 完整知识体系:从数学基础 → 工业级项目(人脸识别/自动驾驶/GANs),内容由浅入深
- 💻 实战为王:每小节配套可运行代码案例(提供完整源码)
- 🎯 零基础友好:用生活案例讲解算法,无需担心数学/编程基础
🚀 特别适合
- 想系统补强AI知识的开发者
- 转型人工智能领域的从业者
- 需要项目经验的学生
2.工厂模式:对象创建的"智能生产线"
2.1定义
工厂模式(Factory Pattern)是一种创建型设计模式,它通过封装对象创建过程,将对象的实例化延迟到子类或专门的工厂类中,从而实现创建者与使用者的解耦。在 Java IO 中,工厂模式主要解决了"如何根据不同条件创建不同类型的流对象"这一核心问题。
2.2UML 图

2.3代码示例:自定义流工厂
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;// 抽象工厂接口
interface InputStreamFactory {InputStream createInputStream() throws IOException;
}// 文件输入流工厂
class FileInputStreamFactory implements InputStreamFactory {private String filePath;public FileInputStreamFactory(String filePath) {this.filePath = filePath;}@Overridepublic InputStream createInputStream() throws IOException {// 实际开发中可在此添加文件权限检查、路径验证等逻辑return new FileInputStream(filePath);}
}// 字节数组输入流工厂
class ByteArrayInputStreamFactory implements InputStreamFactory {private byte[] data;public ByteArrayInputStreamFactory(byte[] data) {this.data = data;}@Overridepublic InputStream createInputStream() {// 可添加数据预处理逻辑return new ByteArrayInputStream(data);}
}// 工厂使用者
class DataProcessor {private InputStreamFactory factory;// 依赖注入:通过构造函数传入具体工厂public DataProcessor(InputStreamFactory factory) {this.factory = factory;}public void process() throws IOException {try (InputStream is = factory.createInputStream()) {byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {// 处理数据...System.out.println("读取到 " + len + " 字节数据");}}}
}// 测试类
public class FactoryPatternDemo {public static void main(String[] args) throws IOException {// 使用文件工厂InputStreamFactory fileFactory = new FileInputStreamFactory("data.txt");DataProcessor fileProcessor = new DataProcessor(fileFactory);fileProcessor.process();// 切换为字节数组工厂,无需修改 DataProcessor 代码byte[] testData = "Hello Factory Pattern".getBytes();InputStreamFactory byteFactory = new ByteArrayInputStreamFactory(testData);DataProcessor byteProcessor = new DataProcessor(byteFactory);byteProcessor.process();}
}
2.4生活类比:饮料自动售货机
工厂模式就像饮料自动售货机:你只需按下"可乐"或"雪碧"的按钮(指定工厂类型),机器内部会自动完成原料调配、灌装等复杂过程(对象创建细节),最后交给你想要的饮料(返回的对象)。作为消费者,你不需要知道饮料是如何制作的,只需关心选择哪种饮料。
2.5IO 中的实际应用:File创建流
JDK 中最典型的工厂模式应用就是 File 类的一系列创建流的方法:
// File 类作为工厂,创建不同类型的流对象
File file = new File("data.txt");
InputStream is = fileInputStream(); // 创建文件输入流
OutputStream os = fileOutputStream(); // 创建文件输出流
Reader reader = new FileReader(file); // 字符输入流
Writer writer = new FileWriter(file); // 字符输出流
为什么这样设计?
-  隐藏创建细节:文件流的创建需要处理文件句柄、操作系统调用等复杂逻辑,工厂方法将这些细节封装起来 
-  统一接口:无论创建哪种流,都通过类似的方法名,降低学习成本 
-  版本兼容:后续 JDK 版本可以在不修改接口的情况下优化创建逻辑 
源码小贴士:查看 FileInputStream 的构造方法可以发现,它实际调用了 FileDescriptor 来操作底层文件句柄,这些复杂逻辑都被工厂方法完美隐藏了。
3.适配器模式:接口转换的"万能转接头"
3.1定义
适配器模式(Adapter Pattern)是一种结构型设计模式,它通过包装一个不兼容接口的对象,使之能够与另一个接口协同工作。在 Java IO 中,适配器模式主要解决不同数据类型(字节与字符)之间的转换问题。
3.2UML 图

3.3代码示例:字节流到字符流的适配器
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;// 字节流接口(被适配者接口)
interface ByteStream {int readByte() throws IOException;void writeByte(int b) throws IOException;
}// 字符流接口(目标接口)
interface CharStream {char readChar() throws IOException;void writeChar(char c) throws IOException;
}// 适配器实现:将字节流适配为字符流
class StreamAdapter implements CharStream {private final ByteStream byteStream;private final String charset; // 支持不同字符集public StreamAdapter(ByteStream byteStream, String charset) {this.byteStream = byteStream;this.charset = charset;}@Overridepublic char readChar() throws IOException {// 字节转字符的核心逻辑(简化版,实际实现需考虑多字节字符)byte[] buffer = new byte[2]; // 假设使用 UTF-8 或 GBK 等多字节编码int bytesRead = 0;// 读取足够的字节来组成一个字符while (bytesRead < buffer.length) {int b = byteStream.readByte();if (b == -1) break; // 流结束buffer[bytesRead++] = (byte) b;}if (bytesRead == 0) return (char) -1; // 表示流结束// 根据指定字符集将字节转换为字符return new String(buffer, 0, bytesRead, charset).charAt(0);}@Overridepublic void writeChar(char c) throws IOException {// 字符转字节byte[] bytes = String.valueOf(c).getBytes(charset);for (byte b : bytes) {byteStream.writeByte(b);}}
}// 字节流实现(被适配者)
class SimpleByteStream implements ByteStream {private final InputStream inputStream;public SimpleByteStream(InputStream inputStream) {this.inputStream = inputStream;}@Overridepublic int readByte() throws IOException {return inputStream.read();}@Overridepublic void writeByte(int b) throws IOException {// 简化实现,实际应连接输出流}
}// 测试
public class AdapterDemo {public static void main(String[] args) throws IOException {byte[] data = "你好,适配器模式!".getBytes(StandardCharsets.UTF_8);ByteStream byteStream = new SimpleByteStream(new ByteArrayInputStream(data));// 将字节流适配为字符流CharStream charStream = new StreamAdapter(byteStream, StandardCharsets.UTF_8);char c;while ((c = charStream.readChar()) != (char) -1) {System.out.print(c); // 正确输出中文字符}}
}
3.4生活类比:电源转接头
适配器模式就像出国旅行用的电源转接头:欧洲的电器插头是圆头的(被适配接口),中国的插座是扁头的(目标接口),转接头(适配器)通过内部线路转换,让欧洲电器能在中国插座上使用。转接头本身不改变电器功能,只是解决接口不兼容问题。
3.5IO 中的实际应用:字节流与字符流的桥梁
Java IO 中最经典的适配器就是字节流与字符流之间的转换类:
// InputStreamReader:将字节输入流适配为字符输入流
InputStream is = new FileInputStream("data.txt");
Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);// OutputStreamWriter:将字节输出流适配为字符输出流
OutputStream os = new FileOutputStream("data.txt");
Writer writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
适配器如何工作?
-  InputStreamReader 内部包含一个 StreamDecoder(实际的适配逻辑) 
-  它读取字节流,根据指定的字符集(如 UTF-8)解码为 Unicode 字符 
-  对外暴露 Reader 接口,使得字符流处理类可以直接操作字节流 
编码问题排查技巧:当你遇到中文乱码时,首先检查 InputStreamReader 使用的字符集是否与文件实际编码一致。这就是适配器在工作时可能出现的"转换错误"。
4.装饰器模式:功能增强的"俄罗斯套娃"
4.1定义
装饰器模式(Decorator Pattern)是一种结构型设计模式,它通过动态地给对象添加额外职责,而无需修改其原始类。与继承相比,装饰器模式提供了更灵活的功能扩展方式。在 Java IO 中,装饰器模式是构建"流家族"的核心骨架。
4.2UML 图

4.3代码示例:自定义加密装饰流
import java.io.*;// 基础装饰器类
class FilterInputStream extends InputStream {protected InputStream in;public FilterInputStream(InputStream in) {this.in = in;}@Overridepublic int read() throws IOException {return in.read(); // 默认转发调用}@Overridepublic void close() throws IOException {in.close();}
}// 缓冲装饰器:添加缓冲功能
class BufferedInputStream extends FilterInputStream {private byte[] buffer;private int pos = 0;private int count = 0;public BufferedInputStream(InputStream in, int bufferSize) {super(in);this.buffer = new byte[bufferSize];}@Overridepublic int read() throws IOException {// 如果缓冲区为空,读取一批数据if (pos >= count) {count = in.read(buffer, 0, buffer.length);if (count == -1) return -1; // 流结束pos = 0;}return buffer[pos++]; // 从缓冲区返回数据}
}// 加密装饰器:添加简单的 XOR 加密功能
class EncryptedInputStream extends FilterInputStream {private final int key; // 加密密钥public EncryptedInputStream(InputStream in, int key) {super(in);this.key = key;}@Overridepublic int read() throws IOException {int data = super.read();return data != -1 ? data ^ key : -1; // 简单异或加密}
}// 使用示例
public class DecoratorDemo {public static void main(String[] args) throws IOException {// 创建基础流InputStream fileStream = new FileInputStream("secret.txt");// 添加缓冲功能InputStream bufferedStream = new BufferedInputStream(fileStream, 1024);// 添加加密功能(套娃式装饰)InputStream encryptedStream = new EncryptedInputStream(bufferedStream, 0x1F);// 读取数据(自动应用缓冲和加密)int data;while ((data = encryptedStream.read()) != -1) {System.out.print((char) data);}// 只需关闭最外层装饰流,会自动传递关闭操作encryptedStream.close();}
}
4.4生活类比:快递包装
装饰器模式就像快递包装过程:商品本身(基础流)可以直接运输,但我们会根据需要添加不同包装:
-  先套上气泡膜(BufferedInputStream 添加缓冲) 
-  再放入防水袋(DataInputStream 添加数据类型处理) 
-  最后装入纸箱(自定义加密装饰器) 
每个包装层都增强了商品的某种特性,但商品本身并未改变。需要时可以轻松添加或移除某个包装层。
4.5IO 中的实际应用:流的多层装饰
Java IO 的流体系几乎全是装饰器模式的应用:
// 多层装饰:基础流 + 缓冲 + 数据类型处理
InputStream is = new FileInputStream("data.bin");
BufferedInputStream bis = new BufferedInputStream(is); // 加速读取
DataInputStream dis = new DataInputStream(bis); // 支持基本类型读取// 读取数据
int num = dis.readInt();
String str = dis.readUTF();
double d = dis.readDouble();
装饰器模式的优势:
-  按需组合功能:需要缓冲就加 BufferedInputStream,需要压缩就加 GZIPInputStream 
-  避免类爆炸:如果用继承实现这些组合,需要 BufferedFileInputStream、BufferedByteArrayInputStream 等无数类 
-  运行时动态调整:可以根据配置文件决定添加哪些装饰器 
性能优化建议:总是先用 BufferedInputStream 装饰基础流,它能将频繁的小 IO 操作合并为批量操作,通常能带来 10 倍以上的性能提升。
5.观察者模式:事件驱动的"订阅-通知"机制
5.1定义
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。在 Java NIO 中,观察者模式是实现高并发 IO 的核心机制。
5.2UML 图

5.3代码示例:简易 NIO Selector 实现
import java.util.*;
import java.nio.channels.SelectableChannel;// 观察者接口
interface ChannelObserver {void onEvent(); // 事件发生时回调SelectableChannel getChannel(); // 获取观察的通道
}// 被观察者(选择器)
class SimpleSelector {private Set<ChannelObserver> observers = new HashSet<>();private Set<SelectableChannel> readyChannels = new HashSet<>();// 注册观察者public void register(ChannelObserver observer) {observers.add(observer);}// 移除观察者public void unregister(ChannelObserver observer) {observers.remove(observer);}// 模拟检测就绪通道public void simulateChannelReady(SelectableChannel channel) {readyChannels.add(channel);}// 选择就绪通道并通知观察者public int select() {if (readyChannels.isEmpty()) return 0;// 通知所有观察就绪通道的观察者for (ChannelObserver observer : observers) {if (readyChannels.contains(observer.getChannel())) {observer.onEvent();}}int count = readyChannels.size();readyChannels.clear(); // 清空就绪集合return count;}
}// 具体观察者:处理读事件
class ReadObserver implements ChannelObserver {private SelectableChannel channel;public ReadObserver(SelectableChannel channel) {this.channel = channel;}@Overridepublic void onEvent() {System.out.println("Channel " + channel + " 可读,开始读取数据...");// 实际读取逻辑}@Overridepublic SelectableChannel getChannel() {return channel;}
}// 测试
public class ObserverDemo {public static void main(String[] args) {SimpleSelector selector = new SimpleSelector();SelectableChannel channel1 = mockChannel();SelectableChannel channel2 = mockChannel();// 注册观察者selector.register(new ReadObserver(channel1));selector.register(new ReadObserver(channel2));// 模拟 channel1 就绪selector.simulateChannelReady(channel1);// 选择就绪通道(会触发 observer.onEvent())int readyCount = selector.select();System.out.println("检测到 " + readyCount + " 个就绪通道");}private static SelectableChannel mockChannel() {return new java.nio.channels.spi.AbstractSelectableChannel(null) {@Overrideprotected void implCloseChannel() {}@Overridepublic SelectionKey register(Selector sel, int ops, Object att) {return null;}};}
}
5.4生活类比:气象站与显示屏
观察者模式就像气象站与多个显示屏:
-  气象站(Selector)持续监测天气数据 
-  多个显示屏(Channel 观察者)订阅气象站 
-  当天气变化(通道就绪),气象站会主动通知所有显示屏更新数据 
这种模式下,显示屏无需不断询问气象站"数据更新了吗",而是被动接收通知,极大节省了资源。
5.5NIO 中的实际应用:Selector 与事件驱动
NIO 的核心组件 Selector 就是观察者模式的完美实现:
// 创建选择器(被观察者)
Selector selector = Selector.open();// 通道注册为观察者,关注读事件
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);// 事件循环:等待就绪事件
while (true) {int readyCount = selector.select(); // 阻塞等待事件if (readyCount == 0) continue;// 处理所有就绪事件Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> iterator = keys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {// 处理连接事件} else if (key.isReadable()) {// 处理读事件}iterator.remove();}
}
为什么 NIO 比传统 IO 高效?
-  非阻塞 + 事件驱动:一个线程可以管理成千上万个通道 
-  避免线程切换开销:传统 IO 每个连接需要一个线程,大量线程切换会消耗 CPU 
-  按需处理:只在通道就绪时才处理,减少无效等待 
NIO 性能优化点:selector.select(1000) 设置超时时间,避免线程永久阻塞;selectedKeys() 返回的集合需要手动清除已处理的键。
6.四种模式对比分析
| 设计模式 | 核心意图 | IO/NIO 典型应用 | 优点 | 缺点 | 
|---|---|---|---|---|
| 工厂模式 | 创建对象,解耦创建与使用 | FileInputStream、File 类的流创建方法 | 隐藏创建细节,统一接口,便于扩展 | 如果产品种类过多,会导致工厂类膨胀 | 
| 适配器模式 | 接口转换,解决不兼容问题 | InputStreamReader、OutputStreamWriter | 复用现有类,无需修改源代码 | 过多适配器会增加系统复杂度 | 
| 装饰器模式 | 动态添加功能,功能组合 | BufferedInputStream、DataInputStream | 灵活组合功能,避免类爆炸 | 多层装饰可能导致调试困难 | 
| 观察者模式 | 事件通知,实现一对多通信 | Selector、SelectionKey | 解耦事件源与处理者,支持高并发 | 通知顺序不确定,可能导致性能问题 | 
如何选择合适的模式?
-  当需要创建对象:如果对象创建复杂且有多种变体 → 工厂模式 
-  当接口不兼容:需要连接两个不同接口 → 适配器模式 
-  当需要增强功能:希望动态添加/移除功能 → 装饰器模式 
-  当需要事件通知:一个对象变化需要通知多个对象 → 观察者模式 
7.总结:设计模式如何塑造 IO/NIO 架构
回顾 Java IO/NIO 的设计,我们会发现这四种模式不是孤立存在的,而是相互配合构成了整个 IO 体系:
-  工厂模式负责创建基础流对象 
-  适配器模式连接字节流与字符流世界 
-  装饰器模式为流添加各种增强功能 
-  观察者模式支撑 NIO 的高并发处理能力 
理解这些模式不仅能帮助我们更好地使用 IO/NIO API,更重要的是掌握"面向接口编程"、"开闭原则"、"组合优于继承" 等设计思想。当你下次设计自己的类库时,不妨思考:哪里可以用工厂模式简化对象创建?哪里可以用装饰器模式提供灵活扩展?
