Java IO流采用装饰器模式,允许通过组合不同流来逐层添加功能。以FileInputStream被BufferedInputStream和DataInputStream装饰为例,其层次结构和功能如下:
1. 基础流:FileInputStream
功能:直接从文件读取原始字节数据。
特点:无缓冲,每次读取需系统调用,效率较低。
2. 第一层装饰:BufferedInputStream
作用:为底层流(如
FileInputStream)添加缓冲功能。原理:
维护一个内存缓冲区,批量读取数据块(默认8192字节)。
读取时优先从缓冲区取数据,减少磁盘访问次数。
优势:显著提升读取效率,尤其适用于频繁小数据读取的场景。
3. 第二层装饰:DataInputStream
作用:将字节流转换为高级数据类型(如
int、double)。原理:
基于底层流(如
BufferedInputStream)读取字节,并按大端序解析为数据类型。提供便捷方法(如
readInt()、readDouble()),避免手动拼接字节。
依赖:需依赖底层流的缓冲功能以保证高效读取。
组合顺序与性能优化
推荐顺序:
FileInputStream → BufferedInputStream→DataInputStream
原因:
缓冲优先:先通过
BufferedInputStream减少磁盘I/O次数。解析后置:
DataInputStream在缓冲基础上解析数据,避免频繁读取。
反例:若先
DataInputStream后BufferedInputStream,解析时仍需多次读取底层流,效率较低。
代码示例
import java.io.*;
public class IODecoratorExample {
public static void main(String[] args) throws IOException {
// 创建基础流
try (FileInputStream fis = new FileInputStream("data.bin");
// 添加缓冲功能
BufferedInputStream bis = new BufferedInputStream(fis);
// 添加数据解析功能
DataInputStream dis = new DataInputStream(bis)) {
// 读取整数(利用缓冲和解析)
int value = dis.readInt();
System.out.println("Read int: " + value);
} // 自动关闭所有流
}
}
关键特性总结
| 装饰器 | 功能 | 核心方法示例 |
|---|---|---|
| BufferedInputStream | 缓冲读写,减少I/O次数 | read()(从缓冲区取数据) |
| DataInputStream | 字节到数据类型的解析 | readInt(), readDouble() |
注意事项
关闭流:只需关闭最外层流(如
DataInputStream),底层流会自动关闭。性能考量:
缓冲区大小可通过构造函数调整(如
new BufferedInputStream(fis, 1024))。过度装饰可能增加内存开销,需按需组合。
异常处理:确保在
try-with-resources中使用流,避免资源泄漏。
应用场景
文件读取:高效读取结构化二进制文件(如音频头、图像元数据)。
网络传输:在Socket流上添加缓冲和解析功能,提升数据传输效率。
通过合理组合装饰器,可以在不修改原有代码的情况下,灵活扩展IO流的功能,同时保持高性能和高可维护性。
DataInputStream 是 Java 中用于从二进制流中读取基本数据类型和字符串的核心类,常用于解析由 DataOutputStream 写入的二进制文件或网络传输的数据。以下是使用方法及注意事项的总结:
一、核心使用步骤
创建基础输入流 通常以
FileInputStream为基础,指向目标文件或数据源。FileInputStream fis = new FileInputStream("data.bin");包装为 DataInputStream 直接通过构造函数将基础流传入,或结合
BufferedInputStream提升效率。// 推荐搭配缓冲流,减少磁盘IO次数
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(fis))) {
// 读取数据
}按顺序读取数据 根据数据写入顺序,调用对应的
read方法(如readInt()、readDouble()等)。
二、常用读取方法
| 方法 | 返回类型 | 作用 |
|---|---|---|
readInt() | int | 读取4字节整数(大端序) |
readLong() | long | 读取8字节长整数 |
readFloat() | float | 读取4字节单精度浮点数 |
readDouble() | double | 读取8字节双精度浮点数 |
readBoolean() | boolean | 读取1字节布尔值 |
readUTF() | String | 读取带长度前缀的字符串 |
readFully(byte[] b) | - | 填充字节数组(需完全读取) |
示例:读取包含 int 和 double 的文件
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("data.bin")))) {
int intValue = dis.readInt(); // 读取整数
double doubleValue = dis.readDouble(); // 读取双精度浮点数
System.out.println("Int: " + intValue + ", Double: " + doubleValue);
}
三、关键注意事项
字节序(Endian)
DataInputStream默认使用大端序(Big-endian),若数据为小端序需手动转换或使用ByteBuffer。异常处理
读取过程中可能抛出
EOFException(文件末尾未读完)或IOException,需捕获或抛出。使用
try-with-resources自动关闭流,避免资源泄漏。
数据对齐 读取顺序必须与数据写入顺序严格一致,否则会导致解析错误。
性能优化
通过
BufferedInputStream包装基础流,减少磁盘/网络IO次数。避免频繁调用单字节读取方法(如
readByte()),优先使用批量读取(如readFully())。
四、典型应用场景
解析二进制文件 读取自定义格式的二进制文件(如配置文件、数据存储文件)。
// 示例:读取含布尔值和字符串的文件
boolean flag = dis.readBoolean();
String json = dis.readUTF();网络数据传输 从
Socket输入流中读取二进制协议数据。DataInputStream dis = new DataInputStream(socket.getInputStream());
int messageType = dis.readInt();
byte[] data = new byte[dis.readInt()];
dis.readFully(data);替代手工字节解析 相比手动拼接字节(如
ByteBuffer),DataInputStream更直观且代码量更少。
五、扩展技巧
读取字节数组 若需解析原始字节,可先用
read(byte[])读取到数组,再进一步处理。byte[] buffer = new byte[1024];
int bytesRead = dis.read(buffer); // 返回实际读取的字节数处理混合数据类型 结合
readFully()和偏移量读取复杂结构(如嵌套对象)。
总结
DataInputStream 通过简单的方法封装了二进制数据的解析逻辑,适用于大多数基本数据类型的读取场景。使用时需注意字节序、异常处理和资源管理,并通过 BufferedInputStream 提升性能。对于复杂二进制协议或高性能需求,可结合 ByteBuffer 或第三方库(如 Protobuf)。




