CodeGym /课程 /JAVA 25 SELF /DataInputStream, DataOutputStream:处理基本类型

DataInputStream, DataOutputStream:处理基本类型

JAVA 25 SELF
第 36 级 , 课程 3
可用

1. 为什么需要 DataInputStream 和 DataOutputStream?

在处理文件时,有时需要存的不只是文本,而是结构化数据:数字、布尔值、基本类型数组。比如,你在写一个简单的游戏,想保存用户进度:分数(int)、当前关卡(int)、游戏时间(double)、是否获胜(boolean)。当然,你也可以按文本形式写成:

12345
5
67.5
true

但这既不方便也不安全:字符串需要解析,格式可能“漂移”,且数字占用空间更大。

思路是把数据以“原始”(二进制)形式写入文件,而非转换为文本。为此,Java 提供了两个非常好用的类:

  • DataOutputStream — 能将基本类型写入到流。
  • DataInputStream — 能从流中读取基本类型。

它们工作在普通字节流(OutputStream/InputStream)之上。也就是说,它们是“上层封装”,不只是写字节,还知道如何把这些字节组装/还原为 intdoubleboolean,甚至 String

它是如何工作的?

普通的 FileOutputStream 可以想象成一条传送带,你需要手动一个字节一个字节地放上去。若要写入整数或字符串,就得自己关心每个元素占多少字节。

DataOutputStream 能让你省心:它就像这条传送带上的机器人。你对它说“写一个数字”或“写一个字符串”,它会自动把数据打包成需要的字节数并写入磁盘。传送带的另一端也有一个机器人 —— DataInputStream —— 它会把这些字节重新还原成原始对象。

为什么这很方便? 因为你不必再考虑 intdoubleboolean 各自需要多少字节。数据存储更紧凑,读写更快,而且没有解析错误或格式问题的风险。

2. 示例:写入和读取基本类型

假设我们要保存(一个假想)游戏的结果:玩家姓名(String)、分数(int)、最好用时(double)、是否获胜(boolean)。

将数据写入文件

import java.io.*;

public class SaveGameData {
    public static void main(String[] args) {
        String fileName = "savegame.bin";
        String playerName = "Alice";
        int score = 12345;
        double recordTime = 67.5;
        boolean isWinner = true;

        try (DataOutputStream dos = new DataOutputStream(
                new FileOutputStream(fileName))) {
            dos.writeUTF(playerName);    // 写入字符串(UTF-8)
            dos.writeInt(score);         // 写入 int(4 字节)
            dos.writeDouble(recordTime); // 写入 double(8 字节)
            dos.writeBoolean(isWinner);  // 写入 boolean(1 字节)
            System.out.println("数据已成功写入文件!");
        } catch (IOException e) {
            System.out.println("写入错误:" + e.getMessage());
        }
    }
}
  • writeUTF(String) — 以 UTF-8 格式写入字符串(开头包含长度)。
  • writeInt(int) — 写入 4 个字节。
  • writeDouble(double) — 写入 8 个字节。
  • writeBoolean(boolean) — 写入 1 个字节(10)。
  • 所有方法都会自动按正确的格式打包数据。

从文件读取数据

import java.io.*;

public class LoadGameData {
    public static void main(String[] args) {
        String fileName = "savegame.bin";

        try (DataInputStream dis = new DataInputStream(
                new FileInputStream(fileName))) {
            String playerName = dis.readUTF();      // 读取字符串
            int score = dis.readInt();              // 读取 int
            double recordTime = dis.readDouble();   // 读取 double
            boolean isWinner = dis.readBoolean();   // 读取 boolean

            System.out.println("玩家姓名:" + playerName);
            System.out.println("分数:" + score);
            System.out.println("时间:" + recordTime);
            System.out.println("是否获胜:" + isWinner);
        } catch (IOException e) {
            System.out.println("读取错误:" + e.getMessage());
        }
    }
}

重要提示:
读取顺序必须与写入顺序一致! 如果先写的是字符串,然后是 int,再接着是 double —— 读取时也必须按相同顺序,否则会得到错误或“乱七八糟”的数据。

3. 支持哪些类型?

DataOutputStreamDataInputStream 支持 Java 的所有主要基本类型:

写入方法 读取方法 数据类型 大小(字节)
writeBoolean(boolean)
readBoolean()
boolean 1
writeByte(int)
readByte()
byte 1
writeShort(int)
readShort()
short 2
writeChar(int)
readChar()
char 2
writeInt(int)
readInt()
int 4
writeLong(long)
readLong()
long 8
writeFloat(float)
readFloat()
float 4
writeDouble(double)
readDouble()
double 8
writeUTF(String)
readUTF()
String (UTF) 可变

说明:
- 对字符串最常使用 writeUTF/readUTF(先写入字符串长度,再写入 UTF-8 字节)。
- 如果要写入数组,先写数组长度,然后逐个写入元素。

4. 进阶示例:保存基本类型数组

写入数组

int[] scores = {100, 200, 300, 400, 500};
try (DataOutputStream dos = new DataOutputStream(
        new FileOutputStream("scores.bin"))) {
    dos.writeInt(scores.length); // 先写入数组长度
    for (int score : scores) {
        dos.writeInt(score);     // 再写入每个元素
    }
}

读取数组

try (DataInputStream dis = new DataInputStream(
        new FileInputStream("scores.bin"))) {
    int length = dis.readInt();      // 读取长度
    int[] scores = new int[length];
    for (int i = 0; i < length; i++) {
        scores[i] = dis.readInt();   // 读取元素
    }
    // 打印数组
    for (int score : scores) {
        System.out.println(score);
    }
}

为什么要先写长度?
因为在读取时我们并不知道写入了多少个数字。把长度写在开头,让文件格式自解释。

5. 重要注意事项与特性

何时应该使用 DataInputStream/DataOutputStream?

  • 当需要保存/加载由基本类型组成的结构化数据时。
  • 用于 Java 程序之间(甚至不同语言,只要你了解格式)的二进制数据交换。
  • 当强调紧凑性与速度时(例如日志、计算结果、大型数值数组)。

不适合的场景:

  • 如果需要人类可读的格式(CSV、JSON、XML)——请使用文本格式。
  • 对于具有复杂嵌套的对象——更适合通过 ObjectOutputStream/ObjectInputStream 进行序列化(这是另一个主题)。

缓冲

DataOutputStreamDataInputStream 本身不带缓冲。如果你要处理大文件、希望提高性能,请再包一层 BufferedOutputStream/BufferedInputStream

try (DataOutputStream dos = new DataOutputStream(
        new BufferedOutputStream(new FileOutputStream("data.bin")))) {
    // ...
}

字符串编码

writeUTF/readUTF 使用一种特殊格式:先写入字符串长度(单位:字节),再写入 UTF-8 内容。不要与直接写入字节数组混淆!

异常

读/写操作可能会抛出 IOException,比如文件不可用、损坏或提前结束。当尝试读取超出已写入的数据时,常见异常为 EOFException。务必使用 try-with-resources,或用 try-catch 进行异常处理。

读写顺序

最常见的错误——读写顺序不一致。如果你写入的顺序是:intdoubleboolean,而读取时按 doubleintboolean 来读,就会得到错误的数据或抛出异常。

6. 常见错误

错误 1:读写顺序被破坏。 如果你更改了方法调用顺序,数据会被错误读取或抛出异常。比如先写了字符串再写数字,但读取时先尝试读取数字——就会出现格式错误。

错误 2:忘记写入数组长度。 如果你写入了基本类型数组,却没有写入其长度,那么在读取时就不知道要读多少个元素,往往导致错误或在末尾出现“多余”的数据。

错误 3:在文件末尾之后继续读取。 如果读取的数据量超过了写入量,会得到 EOFException(end of file)。

错误 4:用 DataInputStream/DataOutputStream 处理普通文本文件。 这些类并非用于读取普通文本文件(例如用系统记事本创建的文件)。如果你试图通过 readInt() 去读取这样的文件——只会得到毫无意义的数据或报错。

错误 5:未关闭流。 如果不使用 try-with-resources,或不手动关闭流,文件可能会被其他程序占用,或者数据未完全落盘。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION