Skip to content

DataOutputStream 写入基本类型

DataOutputStream 是 Java 中写入二进制基本类型数据的标准方式。

DataInputStream 配套使用,可以精确地保存和读取 int、long、double 等基本类型,不会像文本格式那样有精度损失或解析歧义。

基本用法

java
try (DataOutputStream dos = new DataOutputStream(
        new BufferedOutputStream(
            new FileOutputStream("data.bin")))) {
    dos.writeInt(42);             // 4 字节
    dos.writeLong(1234567890L);   // 8 字节
    dos.writeDouble(3.14159);    // 8 字节
    dos.writeBoolean(true);      // 1 字节
    dos.writeUTF("Hello");        // 2 字节长度 + N 字节内容
}

写入方法一览

方法参数类型字节数
writeBoolean(boolean)boolean1
writeByte(int)byte1
writeChar(int)char2
writeShort(int)short2
writeInt(int)int4
writeLong(long)long8
writeFloat(float)float4
writeDouble(double)double8
writeBytes(String)StringN(忽略高 8 位)
writeChars(String)String2×N(每个字符 2 字节)
writeUTF(String)String2+N(长度前缀 + 内容)

writeUTF() 的秘密

writeUTF() 是变长字符串写入的利器:

java
// 写入 "AB中"
// writeUTF:自动计算长度,中文正确
dos.writeUTF("AB中");  // [00,06,41,42,E4,BD,A0] 7字节
//                   ↑ 长度 = 6

// writeChars:每个字符 2 字节
dos.writeChars("AB中"); // [00,41,00,42,4E,2D] 6字节
//                      ↑ 每个字符 2 字节

// writeBytes:只写低 8 位,中文丢失
dos.writeBytes("AB中"); // [41,42] 2字节!

推荐用 writeUTF():它自动添加长度前缀,读取时不需要预先知道字符串长度。

配合 Buffered 使用

永远配合 BufferedOutputStream 使用

java
// ❌ 差:没有缓冲,每次写入都可能触发磁盘 IO
DataOutputStream dos = new DataOutputStream(
    new FileOutputStream("data.bin"));

// ✅ 好:有缓冲
DataOutputStream dos = new DataOutputStream(
    new BufferedOutputStream(
        new FileOutputStream("data.bin")));

实战:自定义文件格式

格式设计

文件头(固定 16 字节)
  [Magic:4字节 "JCFG"] [版本号:4字节] [记录数:4字节] [保留:4字节]

记录(重复 N 次)
  [ID:4字节] [名称:UTF字符串] [年龄:4字节] [分数:8字节]

写入

java
public static void writeRecords(String path, List<Record> records)
        throws IOException {
    try (DataOutputStream dos = new DataOutputStream(
            new BufferedOutputStream(
                new FileOutputStream(path)))) {
        // 文件头
        dos.writeBytes("JCFG");           // Magic
        dos.writeInt(1);                   // 版本号
        dos.writeInt(records.size());      // 记录数
        dos.writeInt(0);                   // 保留字段

        // 记录
        for (Record r : records) {
            dos.writeInt(r.id);
            dos.writeUTF(r.name);
            dos.writeInt(r.age);
            dos.writeDouble(r.score);
        }
    }
}

static class Record {
    int id;
    String name;
    int age;
    double score;
}

读取

java
public static List<Record> readRecords(String path) throws IOException {
    List<Record> records = new ArrayList<>();
    try (DataInputStream dis = new DataInputStream(
            new BufferedInputStream(
                new FileInputStream(path)))) {
        // 读文件头
        byte[] magic = new byte[4];
        dis.readFully(magic);
        if (!"JCFG".equals(new String(magic))) {
            throw new IOException("Invalid file format");
        }
        int version = dis.readInt();
        int count = dis.readInt();
        dis.readInt(); // 跳过保留字段

        // 读记录
        for (int i = 0; i < count; i++) {
            Record r = new Record();
            r.id = dis.readInt();
            r.name = dis.readUTF();
            r.age = dis.readInt();
            r.score = dis.readDouble();
            records.add(r);
        }
    }
    return records;
}

常见错误

读写顺序不一致

java
// ❌ 写入顺序和读取顺序不一致
dos.writeInt(id);
dos.writeUTF(name);
dos.writeDouble(score);

// ❌ 读取顺序错了
int id = dis.readInt();
double score = dis.readDouble();  // 错!应该是 UTF
String name = dis.readUTF();      // 错!读到的是 score 的 8 字节当 UTF 解码

flush()

DataOutputStream 没有缓冲,但底层 BufferedOutputStream 有:

java
try (DataOutputStream dos = new DataOutputStream(
        new BufferedOutputStream(
            new FileOutputStream("data.bin")))) {
    dos.writeInt(42);
    // 写完立即关闭,close() 会自动 flush
}
// 或者手动 flush
dos.flush();

记住这个原则

写什么读什么,写在前读在后。 DataInputStream 和 DataOutputStream 必须配对使用。

基于 VitePress 构建