
目录
[2.2UML 图](#2.2UML 图)
[2.5IO 中的实际应用:File创建流](#2.5IO 中的实际应用:File创建流)
[3.2UML 图](#3.2UML 图)
[3.5IO 中的实际应用:字节流与字符流的桥梁](#3.5IO 中的实际应用:字节流与字符流的桥梁)
[4.2UML 图](#4.2UML 图)
[4.5IO 中的实际应用:流的多层装饰](#4.5IO 中的实际应用:流的多层装饰)
[5.2UML 图](#5.2UML 图)
[5.3代码示例:简易 NIO Selector 实现](#5.3代码示例:简易 NIO Selector 实现)
[5.5NIO 中的实际应用:Selector 与事件驱动](#5.5NIO 中的实际应用:Selector 与事件驱动)
[7.总结:设计模式如何塑造 IO/NIO 架构](#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代码示例:自定义流工厂
            
            
              java
              
              
            
          
          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;
    }
    @Override
    public InputStream createInputStream() throws IOException {
        // 实际开发中可在此添加文件权限检查、路径验证等逻辑
        return new FileInputStream(filePath);
    }
}
// 字节数组输入流工厂
class ByteArrayInputStreamFactory implements InputStreamFactory {
    private byte[] data;
    public ByteArrayInputStreamFactory(byte[] data) {
        this.data = data;
    }
    @Override
    public 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 类的一系列创建流的方法:
            
            
              java
              
              
            
          
          // 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代码示例:字节流到字符流的适配器
            
            
              java
              
              
            
          
          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;
    }
    @Override
    public 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);
    }
    @Override
    public 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;
    }
    @Override
    public int readByte() throws IOException {
        return inputStream.read();
    }
    @Override
    public 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代码示例:自定义加密装饰流
            
            
              java
              
              
            
          
          import java.io.*;
// 基础装饰器类
class FilterInputStream extends InputStream {
    protected InputStream in;
    public FilterInputStream(InputStream in) {
        this.in = in;
    }
    @Override
    public int read() throws IOException {
        return in.read(); // 默认转发调用
    }
    @Override
    public 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];
    }
    @Override
    public 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;
    }
    @Override
    public 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 的流体系几乎全是装饰器模式的应用:
            
            
              java
              
              
            
          
          // 多层装饰:基础流 + 缓冲 + 数据类型处理
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 实现
            
            
              java
              
              
            
          
          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;
    }
    @Override
    public void onEvent() {
        System.out.println("Channel " + channel + " 可读,开始读取数据...");
        // 实际读取逻辑
    }
    @Override
    public 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) {
            @Override
            protected void implCloseChannel() {}
            @Override
            public SelectionKey register(Selector sel, int ops, Object att) {
                return null;
            }
        };
    }
}5.4生活类比:气象站与显示屏
观察者模式就像气象站与多个显示屏:
- 
气象站(Selector)持续监测天气数据 
- 
多个显示屏(Channel 观察者)订阅气象站 
- 
当天气变化(通道就绪),气象站会主动通知所有显示屏更新数据 
这种模式下,显示屏无需不断询问气象站"数据更新了吗",而是被动接收通知,极大节省了资源。
5.5NIO 中的实际应用:Selector 与事件驱动
NIO 的核心组件 Selector 就是观察者模式的完美实现:
            
            
              java
              
              
            
          
          // 创建选择器(被观察者)
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,更重要的是掌握**"面向接口编程"、"开闭原则"、"组合优于继承"** 等设计思想。当你下次设计自己的类库时,不妨思考:哪里可以用工厂模式简化对象创建?哪里可以用装饰器模式提供灵活扩展?