Java InputStream

更新于 2025-12-26

Jakob Jenkov 2020-10-07

Java 的 InputStream 类(java.io.InputStream)表示一个有序的字节流。换句话说,你可以从 Java InputStream 中按顺序读取字节数据。这在从文件或网络连接中读取数据时非常有用。


InputStream 子类

Java 的 InputStream 类是 Java IO API 中所有输入流的基类(超类)。每个 InputStream 的子类通常都有非常特定的用途,但都可以当作 InputStream 来使用。InputStream 的子类包括:


InputStream 与数据源

Java InputStream 通常连接到某个数据源,比如文件、网络连接、管道等。这一点在 Java IO 概述 中有更详细的说明。


Java InputStream 示例

Java InputStream 用于读取基于字节的数据,一次读取一个字节。下面是一个从文件中读取所有字节的 InputStream 示例:

InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt");
int data = inputstream.read();
while(data != -1) {
    // 对 data 做一些处理...
    doSomethingWithData(data);
    data = inputstream.read();
}
inputstream.close();

此示例创建了一个新的 FileInputStream 实例。FileInputStreamInputStream 的子类,因此可以安全地将 FileInputStream 实例赋值给 InputStream 类型的变量(即 inputstream 变量)。


read()

InputStreamread() 方法返回一个 int,其中包含所读取字节的值。例如:

int data = inputstream.read();

要读取 InputStream 中的所有字节,必须持续读取,直到返回值为 -1。该值表示已到达流的末尾,没有更多数据可读。以下是读取所有字节的完整示例:

int data = inputStream.read();
while(data != -1) {
    // 对 data 变量做一些处理
    data = inputStream.read(); // 读取下一个字节
}

某些 InputStream 的子类可能提供其他形式的 read() 方法。例如,DataInputStream 允许你通过 readBoolean()readDouble() 等方法读取 Java 基本类型(如 intlongfloatdoubleboolean 等)。

流结束标志

如果 read() 方法返回 -1,表示已到达流的末尾,即 InputStream 中没有更多数据可读。注意,这里指的是 int 类型的 -1,而不是 byteshort 类型的 -1,二者是有区别的。

当到达流末尾后,应关闭 InputStream


read(byte[])

InputStream 类还包含两个可以将数据读入字节数组的 read() 方法:

  • int read(byte[])
  • int read(byte[], int offset, int length)

read(byte[]) 方法会尝试将尽可能多的字节读入传入的字节数组,最多填满整个数组。该方法返回一个 int,表示实际读取的字节数。如果从 InputStream 中读取的字节数少于数组容量,数组其余部分将保留调用前的内容。务必检查返回值,以确定实际读入了多少字节。

read(byte[], int offset, int length) 方法也读取字节到字节数组中,但从数组的 offset 位置开始,并最多读取 length 个字节。同样,该方法返回实际读取的字节数,使用前需检查该值。

如果已到达流末尾,这两个方法都会返回 -1

以下是如何使用 read(byte[]) 方法的示例:

InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt");
byte[] data = new byte[1024];
int bytesRead = inputstream.read(data);
while(bytesRead != -1) {
    doSomethingWithData(data, bytesRead);
    bytesRead = inputstream.read(data);
}
inputstream.close();

此示例首先创建一个字节数组,然后声明一个 int 变量 bytesRead 用于保存每次 read(byte[]) 调用返回的实际读取字节数,并立即进行第一次读取。

while 循环中,调用 doSomethingWithData() 方法,并传入字节数组和实际读取的字节数。循环末尾再次读取数据。

如何使用 read(byte[], int offset, int length) 方法与此类似,只需将 read(byte[]) 替换为带偏移和长度参数的版本即可。


readAllBytes()

从 Java 9 开始,InputStream 类新增了 readAllBytes() 方法。该方法一次性读取 InputStream 中所有可用字节,并返回一个包含全部字节的字节数组。当你需要通过 FileInputStream 将整个文件读入字节数组时,这个方法非常有用。示例如下:

byte[] fileBytes = null;
try(InputStream input = new FileInputStream("myfile.txt")) {
    fileBytes = input.readAllBytes();
}

读取性能

一次读取一个字节数组比一次只读取一个字节要快得多。性能提升很容易达到 10 倍甚至更高。

具体提升幅度取决于字节数组大小、操作系统、硬件等因素。建议根据目标系统的硬盘缓存大小等特性进行调整。通常,8KB 或更大的缓冲区能带来显著性能提升。但一旦字节数组超过底层系统支持的最优大小,继续增大数组将不再带来明显收益。

你可能需要通过实验测试不同缓冲区大小下的读取性能,以找到最佳值。


通过 BufferedInputStream 实现透明缓冲

你可以使用 Java BufferedInputStreamInputStream 添加透明、自动的缓冲功能。BufferedInputStream 会从底层 InputStream 中一次性读取一大块数据到内部缓冲区。之后,即使你逐字节地从 BufferedInputStream 中读取,也能获得接近批量读取的性能优势。

示例如下:

InputStream input = new BufferedInputStream(
    new FileInputStream("c:\\data\\input-file.txt"),
    1024 * 1024 /* 缓冲区大小 */
);

注意:BufferedInputStreamInputStream 的子类,因此可以在任何需要 InputStream 的地方使用。


mark() 和 reset()

InputStream 类包含两个方法:mark()reset(),但其子类可能支持也可能不支持这两个方法。

如果某个 InputStream 子类支持 mark()reset(),它应当重写 markSupported() 方法并返回 true。如果 markSupported() 返回 false,则表示不支持这两个方法。

  • mark() 方法在流中设置一个标记,记录当前已读取的位置。
  • 调用 reset() 后,流会“回退”到该标记位置,并从此处重新开始读取数据。这意味着某些数据可能会被重复读取。

mark()reset() 通常用于实现解析器。例如,解析器可能需要向前预读一些数据;如果发现不符合预期,就可以通过 reset() 回退并尝试其他匹配规则。


关闭 InputStream

使用完 Java InputStream 后,必须将其关闭。通过调用 close() 方法即可:

InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt");
int data = inputstream.read();
while(data != -1) {
    data = inputstream.read();
}
inputstream.close();

注意:上述代码中的 while 循环会在 read() 返回 -1 时退出,随后调用 close()

然而,上述代码并不完全健壮:如果在读取过程中抛出异常,close() 将不会被执行。为了确保资源被正确释放,应使用 Java 的 try-with-resources 语句。关于 Java IO 异常处理的详细说明,请参阅 Java IO 异常处理教程

使用 try-with-resources 的示例:

try (InputStream inputstream = new FileInputStream("file.txt")) {
    int data = inputstream.read();
    while(data != -1){
        data = inputstream.read();
    }
}

注意:InputStreamtry 后的括号中声明,这表示它由 try-with-resources 语句管理。

当执行线程离开 try 块时,inputstream 会自动关闭。即使 try 块内抛出异常,也会先关闭流,再重新抛出异常。因此,只要使用 try-with-resources,就能确保 InputStream 被正确关闭。


将 InputStream 转换为 Reader

Java InputStream 是基于字节的流。而 Java IO API 还提供了基于字符的输入流,称为 “Readers”。你可以使用 Java InputStreamReaderInputStream 转换为 Reader

更多关于 InputStreamReader 的用法请参见相关链接,这里给出一个快速示例:

InputStream inputStream = new FileInputStream("c:\\data\\input.txt");
Reader inputStreamReader = new InputStreamReader(inputStream);