Java FilterInputStream与FilterOutputStream
FilterInputStream 和 FilterOutputStream 是 Java I/O 体系中基于装饰器模式的核心抽象类,用于在不改变原有流结构的前提下动态增强功能。其常见子类如 BufferedInputStream/OutputStream(提升 I/O 性能)、DataInputStream/OutputStream(跨平台读写基本数据类型)、PrintStream(格式化输出)和 LineNumberInputStream(跟踪行号)等,分别适用于大文件处理、网络通信、日志分析、报告生成等场景。通过合理组合这些过滤流,可灵活构建高效、可维护的 I/O 操作链,同时需注意资源管理、缓冲区设置、异常处理及读写类型一致性等最佳实践。
一、核心概念与设计思想
1. 什么是FilterInputStream和FilterOutputStream?
FilterInputStream 是Java中InputStream的子类,用于"封装其他输入流,并为它们提供额外的功能"。它采用装饰器设计模式,通过组合而非继承来扩展功能。
FilterOutputStream 是OutputStream的子类,用于"封装其他输出流,并为它们提供额外的功能"。同样采用装饰器设计模式。
2. 装饰器模式优势
- 避免继承链膨胀:无需为"缓冲+加密"、"缓冲+数据类型转换"等组合创建大量子类
- 符合开闭原则:对扩展开放,对修改关闭
- 单一职责原则:每个类专注于单一功能(如BufferedOutputStream只负责缓冲)
- 流的可组合性:可以将多种功能组合在一起,如BufferedInputStream + DataInputStream
二、FilterInputStream详解
1. 常用子类及功能
| 子类 | 功能 | 适用场景 |
|---|---|---|
| BufferedInputStream | 为输入流提供缓冲功能,减少I/O操作次数 | 大文件读取、频繁读取场景 |
| DataInputStream | 以与机器无关方式读取基本Java数据类型 | 跨平台数据交换、网络通信 |
| LineNumberInputStream | 跟踪输入流的行号 | 日志分析、代码解析器 |
| PushbackInputStream | 提供单字节回退功能 | 编译器词法分析 |
2. 详细代码示例
(1) BufferedInputStream:提高读取性能
import java.io.*;
public class BufferedInputStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("large_file.txt");
BufferedInputStream bis = new BufferedInputStream(fis, 8192)) { // 8KB缓冲区
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// 处理数据
System.out.print(new String(buffer, 0, bytesRead));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
适用场景:处理大文件时,缓冲流可以显著减少磁盘I/O次数,提升性能。尤其适合频繁读取的场景,如日志分析、大文本文件处理。
(2) DataInputStream:跨平台读取基本数据类型
import java.io.*;
public class DataInputStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("binary_data.bin");
DataInputStream dis = new DataInputStream(fis)) {
int intValue = dis.readInt(); // 读取4字节整数
double doubleValue = dis.readDouble(); // 读取8字节双精度
boolean boolValue = dis.readBoolean(); // 读取1字节布尔
String stringValue = dis.readUTF(); // 读取UTF-8字符串
System.out.println("Read from file: " + intValue + ", " + doubleValue + ", " + boolValue + ", " + stringValue);
} catch (IOException e) {
e.printStackTrace();
}
}
}
适用场景:当需要在不同平台间交换结构化数据时,如网络通信、序列化存储。DataInputStream确保了数据在不同平台上的正确解释。
(3) LineNumberInputStream:跟踪行号
import java.io.*;
public class LineNumberInputStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("log.txt");
LineNumberInputStream lnis = new LineNumberInputStream(fis)) {
int data;
while ((data = lnis.read()) != -1) {
System.out.println("Line " + lnis.getLineNumber() + ": " + (char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
适用场景:日志文件分析、代码解析器等需要跟踪读取位置的场景。可以方便地定位特定行的内容。
(4) PushbackInputStream:回退读取
import java.io.*;
public class PushbackInputStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("source.txt");
PushbackInputStream pis = new PushbackInputStream(fis, 1)) {
int c;
while ((c = pis.read()) != -1) {
if (c == 'A') {
// 读取了'A',但需要回退,以便后续处理
pis.unread(c);
System.out.println("Found 'A', will process it later");
break;
}
}
// 重新读取'A'
System.out.println("After unread: " + (char) pis.read());
} catch (IOException e) {
e.printStackTrace();
}
}
}
适用场景:编译器词法分析、解析器实现。当需要"回看"刚刚读取的字符时非常有用。
三、FilterOutputStream详解
1. 常用子类及功能
| 子类 | 功能 | 适用场景 |
|---|---|---|
| BufferedOutputStream | 为输出流提供缓冲功能,减少I/O操作次数 | 大文件写入、频繁写入场景 |
| DataOutputStream | 以与机器无关方式写入基本Java数据类型 | 跨平台数据交换、网络通信 |
| PrintStream | 提供格式化输出功能 | 生成报告、日志记录、用户界面输出 |
2. 详细代码示例
(1) BufferedOutputStream:提高写入性能
import java.io.*;
public class BufferedOutputStreamExample {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("output.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos, 8192)) { // 8KB缓冲区
String text = "This is a test string to demonstrate BufferedOutputStream. " +
"BufferedOutputStream reduces I/O operations by using a buffer.";
bos.write(text.getBytes());
// 显式刷新缓冲区(可选)
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
适用场景:写入大文件时,缓冲流可以显著减少磁盘I/O次数,提升性能。尤其适合频繁写入的场景,如日志记录、文件生成。
(2) DataOutputStream:跨平台写入基本数据类型
import java.io.*;
public class DataOutputStreamExample {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("data.bin");
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream dos = new DataOutputStream(bos)) {
dos.writeInt(12345); // 写入4字节整数
dos.writeDouble(3.14159); // 写入8字节双精度
dos.writeBoolean(true); // 写入1字节布尔
dos.writeUTF("Hello, DataOutputStream!"); // 写入UTF-8字符串
System.out.println("Data written to file using DataOutputStream.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
适用场景:需要在不同平台间交换结构化数据时,如网络通信、序列化存储。DataOutputStream确保了数据在不同平台上的正确解释。
(3) PrintStream:格式化输出
import java.io.*;
public class PrintStreamExample {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("report.txt");
PrintStream ps = new PrintStream(fos)) {
// 使用printf格式化输出
ps.printf("Report Generated on: %s%n", java.time.LocalDate.now());
ps.printf("Total Records: %d%n", 1000);
ps.printf("Average Value: %.2f%n", 45.678);
ps.printf("Status: %s%n", "Completed");
// 使用print方法输出
ps.println("\nDetailed Report:");
ps.println("Record 1: ID=1001, Value=45.67");
ps.println("Record 2: ID=1002, Value=46.23");
} catch (IOException e) {
e.printStackTrace();
}
}
}
适用场景:生成报告、日志记录、用户界面输出等需要格式化文本的场景。PrintStream提供了方便的格式化方法,如printf。
四、组合使用示例
1. 完整的数据交换示例
import java.io.*;
import java.util.Arrays;
public class DataExchangeExample {
public static void main(String[] args) {
// 1. 使用DataOutputStream写入数据
try (FileOutputStream fos = new FileOutputStream("data.bin");
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream dos = new DataOutputStream(bos)) {
// 写入不同类型数据
dos.writeInt(42);
dos.writeDouble(3.1415926);
dos.writeBoolean(true);
dos.writeUTF("Hello, Filter Streams!");
System.out.println("Data written to file using DataOutputStream.");
} catch (IOException e) {
e.printStackTrace();
}
// 2. 使用DataInputStream读取数据
try (FileInputStream fis = new FileInputStream("data.bin");
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis)) {
// 读取不同类型数据
int intValue = dis.readInt();
double doubleValue = dis.readDouble();
boolean boolValue = dis.readBoolean();
String stringValue = dis.readUTF();
System.out.println("Data read from file:");
System.out.println("Int: " + intValue);
System.out.println("Double: " + doubleValue);
System.out.println("Boolean: " + boolValue);
System.out.println("String: " + stringValue);
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 复杂场景:日志分析与处理
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class LogAnalysisExample {
public static void main(String[] args) {
// 1. 读取日志文件,分析错误行
List<String> errorLines = new ArrayList<>();
try (FileInputStream fis = new FileInputStream("application.log");
BufferedInputStream bis = new BufferedInputStream(fis);
LineNumberInputStream lnis = new LineNumberInputStream(bis);
DataInputStream dis = new DataInputStream(lnis)) {
String line;
while ((line = dis.readUTF()) != null) {
if (line.contains("ERROR")) {
errorLines.add("Line " + lnis.getLineNumber() + ": " + line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
// 2. 将分析结果写入新文件
try (FileOutputStream fos = new FileOutputStream("error_analysis.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
PrintStream ps = new PrintStream(bos)) {
ps.println("Error Analysis Report");
ps.println("=====================");
ps.println("Total errors found: " + errorLines.size());
ps.println();
for (String errorLine : errorLines) {
ps.println(errorLine);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
五、最佳实践与注意事项
1. 资源管理
始终使用try-with-resources,确保流被正确关闭:
// 正确做法
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 处理
}
// 不推荐做法(可能导致资源泄漏)
FileInputStream fis = new FileInputStream("file.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
try {
// 处理
} finally {
bis.close();
fis.close();
}
2. 缓冲区大小设置
- 默认缓冲区大小通常为8KB,可以根据实际需求调整
- 对于大文件处理,可以增大缓冲区(如16KB、32KB)
- 对于网络I/O,缓冲区大小应与网络传输特性匹配
BufferedInputStream bis = new BufferedInputStream(fis, 32768); // 32KB缓冲区
3. 异常处理
- 正确捕获并处理IOException
- 避免在finally块中抛出异常,可能导致原始异常被掩盖
try {
// I/O操作
} catch (IOException e) {
// 处理异常
} finally {
// 确保流关闭
}
4. 与基本流的组合
- 通常先创建基本流(如FileInputStream),再用装饰流包装
- 例如:FileInputStream → BufferedInputStream → DataInputStream
5. 性能考虑
- 不要过度装饰:仅在需要特定功能时才使用装饰流
- 避免不必要的缓冲:对于小文件或一次性读写,可能不需要缓冲
- 正确选择流类型:根据数据类型和应用场景选择合适的流
六、常见问题与解决方案
1. 数据类型不匹配问题
问题:使用DataInputStream读取的数据类型与DataOutputStream写入的不匹配
解决方案:确保读写使用相同的数据类型
// 写入
dos.writeInt(123);
// 读取(必须是int类型)
int value = dis.readInt(); // 正确
// int value = dis.readDouble(); // 错误!会导致数据错误
2. 缓冲区大小设置不当
问题:缓冲区太小导致频繁I/O,太大导致内存浪费
解决方案:根据实际应用场景调整缓冲区大小
// 根据文件大小和系统内存设置合适的缓冲区
int bufferSize = 8192; // 8KB,通常足够
BufferedInputStream bis = new BufferedInputStream(fis, bufferSize);
3. 跨平台数据问题
问题:在不同平台间传输二进制数据时出现问题
解决方案:使用DataInputStream/DataOutputStream进行跨平台数据交换
// 写入
dos.writeInt(12345);
dos.writeDouble(3.14159);
// 读取(在不同平台都能正确读取)
int intValue = dis.readInt();
double doubleValue = dis.readDouble();
4. 流关闭顺序问题
问题:装饰流和基本流关闭顺序错误
解决方案:使用try-with-resources自动管理关闭顺序
// 正确的嵌套结构
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 处理
}
七、总结
FilterInputStream和FilterOutputStream是Java I/O体系的核心设计,通过装饰器模式提供了强大的功能扩展能力。理解它们的适用场景和正确使用方法对开发高性能、可维护的Java应用至关重要。
- BufferedInputStream/BufferedOutputStream:用于性能优化,减少I/O操作次数
- DataInputStream/DataOutputStream:用于跨平台数据交换,确保数据正确解释
- LineNumberInputStream:用于跟踪行号,适合日志分析
- PrintStream:用于格式化输出,方便生成报告和日志
在实际应用中,根据具体需求选择合适的流组合,遵循最佳实践,可以显著提升应用的性能和可维护性。记住,装饰器模式的核心是组合而非继承,这使Java I/O体系具有极高的灵活性和可扩展性。
- 感谢你赐予我前进的力量

