讲讲 Java中的节点流(Node Stream)和处理流(Processing Stream)?
在Java的I/O(输入/输出)流体系中,节点流(Node Stream)和处理流(Processing Stream)是两个非常核心的概念。它们的设计体现了面向对象设计中的装饰器模式(Decorator Pattern)。
下面我们用通俗易懂的语言,结合代码和图解,来彻底搞懂这两个概念。
一、 节点流(Node Stream)
1. 什么是节点流?
节点流是直接与数据源或目的地相连的流。这个“数据源”或“目的地”可以是文件、内存、网络连接等。
你可以把它想象成直接插在水源(如水井、水库)上的水管。
2. 特点:
- 直接与特定物理介质(如磁盘文件、内存数组、网络端口)关联。
- 其构造方法的参数通常是物理介质的路径、文件对象或内存数组。
- 只提供最基本的读写功能(通常是按字节或字符逐个读写),性能较低。
3. 常用节点流分类:
| 介质类型 | 输入节点流(读) | 输出节点流(写) |
|---|---|---|
| 文件 (File) | FileInputStream / FileReader |
FileOutputStream / FileWriter |
| 内存数组 (Array) | ByteArrayInputStream / CharArrayReader |
ByteArrayOutputStream / CharArrayWriter |
| 管道 (Thread Pipe) | PipedInputStream / PipedReader |
PipedOutputStream / PipedWriter |
二、 处理流(Processing Stream / Wrapper Stream)
1. 什么是处理流?
处理流(也叫包装流)不能直接连接到数据源,它必须建立在已存在的流(节点流或另一个处理流)之上。
你可以把它想象成接在水管上的过滤器、加热器或水表。它不负责直接去水井里取水,而是对流经它的水进行加工(过滤、加热、计量)。
2. 特点:
- 不能独立存在,构造方法的参数必须是另一个流对象(通常是
InputStream/OutputStream/Reader/Writer的子类)。 - 它的目的是消除不同节点流的实现差异,并提供更强大、更便捷的读写功能(如缓冲、格式化、对象序列化、字符集转换)。
- 使用了装饰器模式,使得多个处理流可以嵌套使用。
3. 常用处理流分类:
| 功能类型 | 输入处理流 | 输出处理流 | 作用 |
|---|---|---|---|
| 缓冲流 (Buffered) | BufferedInputStream / BufferedReader |
BufferedOutputStream / BufferedWriter |
减少I/O次数,提高读写效率(极常用) |
| 转换流 (Bridge) | InputStreamReader |
OutputStreamWriter |
字节流转字符流,解决乱码/字符集问题 |
| 数据流 (Data) | DataInputStream |
DataOutputStream |
支持直接读写Java基本数据类型(int, double等) |
| 对象流 (Object) | ObjectInputStream |
ObjectOutputStream |
实现对象的序列化与反序列化 |
| 打印流 (Print) | - | PrintStream / PrintWriter |
方便格式化输出(如 System.out) |
三、 核心区别对比
| 对比维度 | 节点流 (Node Stream) | 处理流 (Processing Stream) |
|---|---|---|
| 连接对象 | 直接连接到数据源(文件、内存等) | 连接在已有的流之上(包装其他流) |
| 构造方法参数 | 文件路径、File 对象、内存数组等 |
其他 Stream / Reader / Writer 对象 |
| 主要职责 | 实现物理读写操作(最底层) | 提高效率(如缓冲)或增加功能(如序列化) |
| 举例 | FileInputStream("a.txt") |
BufferedInputStream(new FileInputStream("a.txt")) |
四、 代码实战:如何配合使用?
在实际开发中,我们通常将一个节点流作为底座,然后套上一个或多个处理流。
示例:带缓冲的高效文件读取
java
import java.io.*;
public class StreamDemo {
public static void main(String[] args) {
// 1. 创建节点流:直接指向文件(水管直接插在井里)
try (FileInputStream fis = new FileInputStream("test.txt");
// 2. 创建处理流(缓冲流):包装节点流(套上一个加热器,提供缓存,提高效率)
BufferedInputStream bis = new BufferedInputStream(fis);
// 3. 创建处理流(数据流):包装缓冲流(再套一个水表,可以直接读特定类型数据)
DataInputStream dis = new DataInputStream(bis)) {
// 读取数据
int temp;
while ((temp = dis.read()) != -1) {
System.out.print((char) temp);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
关于资源关闭(Close)的注意点:
在上面的代码中,我们使用了 Java 7 的 try-with-resources 语法。如果手动关闭流,只需要关闭最外层的处理流即可。
因为最外层处理流的 close() 方法内部会自动调用它所包装的底层流的 close() 方法。
五、 为什么要这样设计?(装饰器模式的妙处)
如果 Java 不区分“节点流”和“处理流”,我们要实现“带缓冲的文件读取”和“带缓冲的内存读取”,就需要写:
BufferedFileInputStreamBufferedByteArrayInputStream
如果要实现“支持数据类型的网络流读取”,又需要写:
DataSocketInputStream
这会导致类爆炸(类的数量急剧膨胀,且代码大量重复)。
通过将流分为节点流和处理流(装饰器模式):
- 节点流只关注“从哪里读/写到哪里”(File, Memory, Socket)。
- 处理流只关注“如何加工”(Buffering, Encryption, Serialization)。
两两自由组合:
BufferedInputStream+FileInputStream= 带缓冲的文件读。BufferedInputStream+ByteArrayInputStream= 带缓冲的内存读。ObjectInputStream+FileInputStream= 从文件反序列化对象。
极大地提高了代码的灵活性和可复用性。