数据类型处理流讲解
DataInputStream
和 DataOutputStream
是 Java I/O 中处理基本数据类型的核心工具,它们允许你直接读写原始数据类型(如 int, double, boolean 等),而不是只处理字节或字符。这两个类属于过滤流,需要包装在字节流上使用。
一、核心功能与特点
DataOutputStream 核心功能
// 写入各种基本数据类型
void writeBoolean(boolean v)
void writeByte(int v) // 写入8位
void writeShort(int v) // 写入16位
void writeChar(int v) // 写入16位字符
void writeInt(int v) // 写入32位整数
void writeLong(long v) // 写入64位长整数
void writeFloat(float v) // 写入32位浮点数
void writeDouble(double v) // 写入64位双精度
void writeUTF(String str) // 写入UTF-8编码字符串
DataInputStream 核心功能
// 读取各种基本数据类型
boolean readBoolean()
byte readByte()
short readShort()
char readChar()
int readInt()
long readLong()
float readFloat()
double readDouble()
String readUTF() // 读取UTF-8字符串
关键特性
二进制格式:数据以紧凑二进制存储(非人类可读)
平台无关:使用大端序(Big-Endian),保证跨平台一致性
类型安全:严格区分数据类型
高效存储:比文本格式节省50-75%空间
流装饰:必须包装在基础字节流上使用
二、使用示例:读写混合数据类型
写入数据示例
try (DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.bin")))) {// 写入各种类型数据dos.writeUTF("用户数据"); // UTF字符串dos.writeInt(1001); // 用户IDdos.writeBoolean(true); // 激活状态dos.writeDouble(1250.75);// 账户余额dos.writeUTF("2023-10-01"); // 日期// 写入数组int[] scores = {85, 92, 78};dos.writeInt(scores.length); // 先写长度for (int score : scores) {dos.writeInt(score);}
}
读取数据示例
try (DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream("data.bin")))) {// 按写入顺序读取String title = dis.readUTF();int userId = dis.readInt();boolean isActive = dis.readBoolean();double balance = dis.readDouble();String date = dis.readUTF();// 读取数组int length = dis.readInt();int[] scores = new int[length];for (int i = 0; i < length; i++) {scores[i] = dis.readInt();}System.out.println(title);System.out.println("用户ID: " + userId);System.out.println("激活状态: " + isActive);System.out.printf("余额: $%.2f%n", balance);System.out.println("最后登录: " + date);System.out.println("分数: " + Arrays.toString(scores));
}
三、底层数据格式解析
1. 基本数据类型存储大小
数据类型 | 大小 (字节) | 存储格式 |
---|---|---|
boolean | 1 | 0x00(false) 或 0x01(true) |
byte | 1 | 原始8位值 |
short | 2 | 大端序16位整数 |
char | 2 | UTF-16编码单元 |
int | 4 | 大端序32位整数 |
long | 8 | 大端序64位整数 |
float | 4 | IEEE 754 单精度浮点 |
double | 8 | IEEE 754 双精度浮点 |
String(UTF) | 2 + n | 前2字节为长度,后跟UTF-8 |
2. 文件结构示例
文件: data.bin 的二进制结构
00000000: 00 0C 用户数据 // UTF头: 00 0C = 12字节长度
0000000C: 00 00 03 E9 // int 1001 (0x3E9)
00000010: 01 // boolean true
00000011: 40 93 86 00 00 00 00 00 // double 1250.75
00000019: 00 0A 2023-10-01 // 10字节日期字符串
00000023: 00 00 00 03 // 数组长度3
00000027: 00 00 00 55 // 85
0000002B: 00 00 00 5C // 92
0000002F: 00 00 00 4E // 78
四、高级应用技巧
1. 自定义数据序列化
// 序列化用户对象
public void writeUser(User user, DataOutputStream dos) throws IOException {dos.writeUTF(user.getName());dos.writeInt(user.getAge());dos.writeDouble(user.getSalary());dos.writeBoolean(user.isMarried());
}
// 反序列化
public User readUser(DataInputStream dis) throws IOException {return new User(dis.readUTF(),dis.readInt(),dis.readDouble(),dis.readBoolean());
}
2. 数据对齐与填充
// 固定长度记录写入
public void writeFixedRecord(DataOutputStream dos, Record record) throws IOException {dos.writeInt(record.getId());dos.writeUTF(record.getName());// 填充剩余空间int bytesWritten = 4 + 2 + record.getName().getBytes(StandardCharsets.UTF_8).length;int padding = 100 - bytesWritten; // 每条记录100字节for (int i = 0; i < padding; i++) {dos.writeByte(0); // 填充空字节}
}
3. 二进制数据校验
// 写入带校验和的数据
public void writeWithChecksum(DataOutputStream dos, byte[] data) throws IOException {// 计算校验和int checksum = 0;for (byte b : data) {checksum += b & 0xFF;}dos.writeInt(data.length);dos.write(data);dos.writeInt(checksum);
}
// 读取并验证
public byte[] readWithChecksum(DataInputStream dis) throws IOException {int length = dis.readInt();byte[] data = new byte[length];dis.readFully(data); // 读取完整字节数组int expectedChecksum = dis.readInt();int actualChecksum = 0;for (byte b : data) {actualChecksum += b & 0xFF;}if (actualChecksum != expectedChecksum) {throw new IOException("数据校验失败");}return data;
}
五、性能优化实践
1. 批量读写优化
// 批量写入整数数组
public void writeIntArray(DataOutputStream dos, int[] array) throws IOException {dos.writeInt(array.length);for (int i = 0; i < array.length; i += 1024) {int chunkSize = Math.min(1024, array.length - i);// 逐个写入比写入字节数组慢10倍for (int j = 0; j < chunkSize; j++) {dos.writeInt(array[i + j]);}}
}
2. 内存映射文件加速
// 使用MappedByteBuffer处理大数据
try (RandomAccessFile raf = new RandomAccessFile("bigdata.bin", "rw")) {MappedByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 100); // 100MB// 直接操作缓冲区while (buffer.hasRemaining()) {int value = buffer.getInt();// 处理数据...}
}
六、使用注意事项
严格顺序匹配:
// 写入顺序 dos.writeUTF(name); dos.writeInt(age); // 读取必须相同顺序 String n = dis.readUTF(); int a = dis.readInt();
EOF处理:
try {while (true) {String data = dis.readUTF();// 处理数据} } catch (EOFException e) {// 正常结束:文件读取完毕 }
字符编码问题:
// 错误:直接处理非ASCII字符 dos.writeChars("中文"); // 使用UTF-16,浪费空间 // 正确:统一使用UTF-8 dos.writeUTF("中文"); // 更紧凑的UTF-8
字节序问题:
// 如需小端序处理,使用ByteBuffer转换 ByteBuffer buffer = ByteBuffer.allocate(4); buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(value); dos.write(buffer.array());
七、适用场景对比
场景 | 推荐方案 | 优点 | 缺点 |
---|---|---|---|
配置文件 | Properties类 | 人类可读,易修改 | 效率低,无类型信息 |
简单文本数据 | BufferedReader/Writer | 易处理行数据 | 需要类型转换 |
结构化二进制数据 | DataInput/OutputStream | 高效,类型安全 | 二进制不可读 |
复杂对象持久化 | ObjectInput/OutputStream | 直接序列化对象 | 版本兼容问题 |
高性能大文件处理 | NIO Channel + Buffer | 零拷贝,内存映射 | 编程复杂 |
八、最佳实践总结
始终包装缓冲流:
// 正确用法 new DataInputStream(new BufferedInputStream(new FileInputStream("data.bin")) );
处理版本兼容:
// 文件头写入版本号 dos.writeInt(2); // 版本2
重要数据添加校验:
// 写入CRC32校验 dos.writeInt(data.length); dos.write(data); dos.writeLong(crc32.getValue());
处理字节顺序:
// 明确字节序 if (dis.readByte() == 0x01) {// 小端序数据// 需要特殊处理 }
关闭资源安全:
try (DataOutputStream dos = ...) {// 自动关闭所有包装流 }
DataInputStream
和 DataOutputStream
提供了在 Java 中处理原始数据类型的高效方式,特别适合存储结构化数据、游戏存档、科学数据记录等场景。掌握它们的使用能显著提升二进制数据处理效率!