在Java中,InputStream
和OutputStream
是进行流式输入输出的核心类,它们提供了处理字节数据的基本操作。然而,在实际开发中,我们常常需要对流进行增强,比如添加缓冲功能、加密、签名等。传统的继承方式会导致子类数量爆炸,变得难以管理。为了解决这一问题,Java采用了Filter模式(也叫装饰器模式),允许通过组合多个装饰器来扩展流的功能,而无需修改原有类。
1. 输入流(InputStream)和输出流(OutputStream)
Java的IO库提供了两种主要的流类型:InputStream
和OutputStream
。这两者都用于处理字节数据的读写操作。
-
InputStream:从数据源(如文件、网络、内存等)读取数据。
-
OutputStream:将数据写入目标(如文件、网络、内存等)。
1.1 基本的输入流
以下是几种常见的InputStream
的实现类:
-
FileInputStream
:从文件读取数据。 -
ServletInputStream
:从HTTP请求读取数据。 -
Socket.getInputStream()
:从TCP连接读取数据。
然而,如果我们想为FileInputStream
增加额外的功能(比如缓冲、签名或加密),直接使用继承方式会导致代码的复杂度急剧增加。例如,为FileInputStream
添加缓冲功能,可能需要创建多个子类:
java
class BufferedFileInputStream extends FileInputStream { ... }
class DigestFileInputStream extends FileInputStream { ... }
class CipherFileInputStream extends FileInputStream { ... }
如果需要组合这些功能(例如同时添加缓冲和加密),则需要更多的子类,例如:
java
class BufferedDigestFileInputStream extends BufferedFileInputStream { ... }
class BufferedCipherFileInputStream extends BufferedFileInputStream { ... }
这种方式显然会导致子类数量膨胀,难以维护。
2. 使用Filter模式(装饰器模式)
为了解决继承导致的代码复杂性,Java采用了Filter模式(又称装饰器模式)。通过这个模式,我们可以在运行时动态地为InputStream
或OutputStream
添加功能,而无需修改原有的类。
2.1 基本思想
Filter模式
通过将一个流(如FileInputStream
)“包装”成另一个流(如BufferedInputStream
、GZIPInputStream
),允许我们以更灵活的方式组合不同的功能。
假设我们有一个FileInputStream
对象,我们希望通过以下方式来增强它:
-
给流添加缓冲功能,提高读取效率;
-
处理压缩数据,直接读取解压缩后的内容。
我们可以按如下方式嵌套多个流:
java
InputStream file = new FileInputStream("test.gz"); // 基础数据源
InputStream buffered = new BufferedInputStream(file); // 添加缓冲功能
InputStream gzip = new GZIPInputStream(buffered); // 解压缩功能
通过这种方式,我们将多个功能组合成一个流,而最终得到的对象仍然是InputStream
,我们可以像使用普通InputStream
一样读取数据。
2.2 Filter模式示意图
plaintext
┌────────────────────────┐
│ GZIPInputStream │
│┌──────────────────────┐│
││BufferedFileInputStream││
││┌────────────────────┐││
│││ FileInputStream │││
││└────────────────────┘││
└───────────────────────┘
2.3 这种模式的优势
-
灵活组合:我们可以动态地将多个装饰器组合在一起,以实现不同的功能,而不需要大量创建子类。
-
代码复用:相同的装饰器可以多次使用,提高代码复用性。
-
减少类爆炸:无需为每个功能的组合创建新的子类,避免类数量过多,代码更加简洁。
3. 自定义FilterInputStream
Java的IO库提供了FilterInputStream
和FilterOutputStream
作为基础类,可以方便地自定义功能。以下是一个自定义FilterInputStream
的示例,我们创建一个CountInputStream
类,能够统计读取的字节数:
3.1 代码示例
java
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
byte[] data = "hello, world!".getBytes("UTF-8");
try (CountInputStream input = new CountInputStream(new ByteArrayInputStream(data))) {
int n;
while ((n = input.read()) != -1) {
System.out.println((char)n);
}
System.out.println("Total read " + input.getBytesRead() + " bytes");
}
}
}
class CountInputStream extends FilterInputStream {
private int count = 0;
CountInputStream(InputStream in) {
super(in);
}
public int getBytesRead() {
return this.count;
}
@Override
public int read() throws IOException {
int n = in.read();
if (n != -1) {
this.count++;
}
return n;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int n = in.read(b, off, len);
if (n != -1) {
this.count += n;
}
return n;
}
}
3.2 解释
-
CountInputStream
继承了FilterInputStream
,并重写了read()
方法来统计读取的字节数。 -
使用
CountInputStream
时,可以统计读取的字节总数,通过调用getBytesRead()
方法。
3.3 资源管理
使用try-with-resources
语法确保流在使用完毕后被正确关闭。这不仅关闭最外层流,还会递归关闭所有嵌套流,确保资源得到释放,避免资源泄漏。
4. 小结
Java的IO标准库通过Filter模式提供了灵活的方式来扩展InputStream
和OutputStream
的功能。我们可以将一个基础的流对象与多个功能性流(如缓冲、加密、解压缩等)组合,动态地增加功能。这种方式不仅避免了继承方式带来的类爆炸问题,还提高了代码的复用性和灵活性。
4.1 主要概念
-
Filter模式:通过组合多个流来动态增加功能。
-
装饰器模式:使用装饰器包装基础流对象,添加附加功能。
-
FilterInputStream
和FilterOutputStream
:为自定义功能提供基础类,支持流功能的扩展。
通过这些机制,Java的IO系统变得更加强大、灵活和可扩展。
希望通过本文,你能更好地理解Filter模式
在Java中的应用,以及如何通过组合流对象来增强功能。如果你在开发中遇到类似的需求,尝试使用这种模式可以让你的代码更加简洁和高效。