1. 实训目标

  1. 理解 DataInputDataOutput 接口的作用和设计理念。
  2. 掌握 DataInputStreamDataOutputStream 这两个核心实现类的使用。
  3. 学会使用 DataInput / DataOutput 进行基本数据类型的读写操作。
  4. 理解数据持久化和网络传输中序列化的基本概念。

2. 核心概念

DataInputDataOutput 是 Java I/O 中的一对接口,位于 java.io 包,核心作用是定义**“结构化数据的读写规范”——即如何读取/写入 Java 基本数据类型(如 intfloatboolean)和字符串,且保证数据的跨平台兼容性**。

1. 为什么需要这两个接口?

底层字节流(如 InputStream/OutputStream)只能读写 byte 数组,无法直接操作基本数据类型:

  • 比如要写一个 int(4 字节)到文件,直接用 OutputStream 需手动把 int 拆成 4 个 byte
  • 不同操作系统的字节序(大端/小端)不同,手动拆分可能导致跨平台读取乱码。

DataInput/DataOutput 接口统一了这些操作:

  • 提供专门方法直接读写 intfloatString 等;
  • 规定了统一的字节序(大端序),确保不同平台(Windows/Linux/Mac)写入的数据能正确读取。

2. 核心定义与常用方法

DataInput 接口(读取结构化数据)

定义“从数据源读取基本类型和字符串”的规范,常用方法:

方法 作用 备注
readBoolean() 读取 1 字节,返回 boolean 0false,非 0true
readByte() 读取 1 字节,返回 byte 范围:-128~127
readInt() 读取 4 字节,返回 int 按大端序解析
readFloat() 读取 4 字节,返回 float 遵循 IEEE 754 标准
readUTF() 读取 UTF-8 编码的字符串 先读 2 字节表示字符串长度,再读内容
readFully(byte[] b) 读取指定长度的字节到数组 b 必须读满数组长度,否则阻塞

DataOutput 接口(写入结构化数据)

定义“向目的地写入基本类型和字符串”的规范,常用方法(与 DataInput 对应):

方法 作用 备注
writeBoolean(boolean v) 写入 1 字节表示 boolean false0true1
writeByte(int v) 写入 1 字节(取 v 的低 8 位) 兼容 byteint 输入
writeInt(int v) 写入 4 字节表示 int 按大端序写入
writeFloat(float v) 写入 4 字节表示 float 遵循 IEEE 754 标准
writeUTF(String s) 写入 UTF-8 编码的字符串 先写 2 字节表示字符串长度,再写内

注意DataInput / DataOutput接口,不能直接实例化。我们通常使用它们的实现类,最常用的就是 DataInputStreamDataOutputStream

3. 常用实现类

DataOutputStream

  • 作用: 装饰一个 OutputStream,为其增加写入基本数据类型的能力。
  • 构造方法: public DataOutputStream(OutputStream out)
  • 示例:
    1
    2
    FileOutputStream fileOut = new FileOutputStream("data.bin");
    DataOutputStream dataOut = new DataOutputStream(fileOut);

DataInputStream

  • 作用: 装饰一个 InputStream,为其增加读取基本数据类型的能力。

  • 构造方法: public DataInputStream(InputStream in)

  • 示例:

    1
    2
    FileInputStream fileIn = new FileInputStream("data.bin");
    DataInputStream dataIn = new DataInputStream(fileIn);

重要原则:使用 DataOutputStream 写入的数据,必须使用 DataInputStream 按照完全相同的顺序来读取,否则会导致数据解析错误或 EOFException (文件结束异常)。

4. 实践案例:学生信息序列化与反序列化

在这个案例中,你将创建一个程序,它能将一个学生对象的信息(学号、姓名、年龄、身高、是否毕业)写入到一个二进制文件中(序列化),然后再从该文件中读取出来并打印到控制台(反序列化)。

步骤 1: 创建 Student

首先,我们创建一个简单的 Student 类来封装数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Student.java
public class Student {
private int id;
private String name;
private int age;
private double height;
private boolean isGraduated;

// 补全:构造函数
// 补全:Getter 和 Setter 方法
// 补全:toString 方法,方便打印

}

步骤 2: 编写主程序 DataIODemo.java

这个程序将包含两个主要功能:

  1. writeStudentData(): 将一个 Student 对象写入文件。
  2. readStudentData(): 从文件中读取数据并重建一个 Student 对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.io.*;

public class DataIODemo {

private static final String FILE_NAME = "student_data.bin";

/**
* 将 Student 对象的数据写入文件
* @param student 要写入的学生对象
* @throws IOException 如果发生 I/O 错误
*/
public static void writeStudentData(Student student) throws IOException {

DataOutputStream dos = new DataOutputStream(new FileOutputStream(FILE_NAME));
dos.writeInt(student.getId());
dos.writeDouble(student.getHeight());
dos.writeUTF(student.getName());
dos.writeInt(student.getAge());
dos.writeBoolean(student.isGraduated());
}

/**
* 从文件中读取数据并构建一个 Student 对象
* @return 从文件中读取的学生对象
* @throws IOException 如果发生 I/O 错误
*/
public static Student readStudentData() throws IOException {

DataInputStream dis = new DataInputStream(new FileInputStream(FILE_NAME));
int id = dis.readInt();
String name = dis.readUTF();
int age = dis.readInt();
double height = dis.readDouble();
boolean isGraduated = dis.readBoolean();

return new Student(id, name, age, height, isGraduated);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 创建一个学生对象
Student studentToWrite = new Student(101, "张三", 22, 1.75, false);
System.out.println("准备写入的数据: " + studentToWrite);

// 2. 将学生对象写入文件
writeStudentData(studentToWrite);
System.out.println("数据写入成功!");


// 3. 从文件中读取学生对象
Student studentRead = readStudentData();
System.out.println("\n从文件中读取的数据: " + studentRead);

步骤 3: 运行与分析

  1. 将上述两个 Java 文件放在同一个目录下。
  2. 编译并运行 DataIODemo
    1
    2
    javac Student.java DataIODemo.java
    java DataIODemo
  3. 你将看到类似以下的输出:
    1
    2
    3
    4
    准备写入的数据: Student{id=101, name='张三', age=22, height=1.75, isGraduated=false}
    数据写入成功!

    从文件中读取的数据: Student{id=101, name='张三', age=22, height=1.75, isGraduated=false}
  4. 同时,在你的项目目录下会生成一个名为 student_data.bin 的二进制文件。你可以用文本编辑器打开它,但内容会是乱码,因为它不是文本文件而是二进制数据。

代码关键点解析:

  • 写入顺序: writeInt -> writeUTF -> writeInt -> writeDouble -> writeBoolean
  • 读取顺序: readInt -> readUTF -> readInt -> readDouble -> readBoolean
  • 顺序必须严格一致:这是使用 DataInput/DataOutput 的黄金法则。如果你先写了一个 int,然后读的时候却先用 readDouble,程序会试图从流的起始位置解析 8 个字节作为 double,这必然会得到错误的值,并且后续的所有读取都会错位。
  • 与 Java 序列化的区别: 本实训中手动读写字段的方式也可以称为一种“序列化”。Java 还提供了更强大的 Serializable 接口,它能自动序列化对象的所有字段。DataInput/DataOutput 给了你更精细的控制,但代码量也更大。

5. 扩展与思考

  1. 练习: 修改 Student 类,增加一个 String address (地址) 字段。
  2. 探索: 了解 Java 的 ObjectOutputStreamObjectInputStream,并比较它们与 DataOutputStream / DataInputStream 的异同和适用场景。