Java InputStream 类(java.io.InputStream)表示有序的字节流。换句话说,你可以从 Java InputStream 中读取有序字节序列的数据。这在从文件读取数据或通过网络接收数据时非常有用。
Java InputStream 类是 Java IO API 中所有输入流的基类(超类)。InputStream 的每个子类通常都有非常特殊的用途,但都可以作为 InputStream 使用。InputStream 子类有:
ByteArrayInputStream
FileInputStream
PipedInputStream
BufferedInputStream
FilterInputStream
PushbackInputStream
DataInputStream
ObjectInputStream
SequenceInputStream
Java InputStream 通常连接到某个数据源,如文件、网络连接、管道等。Java IO 概述一文对此也有更详细的解释。
Java InputStream 用于读取基于字节的数据,每次读取一个字节。下面是一个从文件中读取所有字节的 Java InputStream 示例:
InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt"); int data = inputstream.read(); while(data != -1) { //do something with data... doSomethingWithData(data); data = inputstream.read(); } inputstream.close();
本例创建了一个新的 FileInputStream 实例。FileInputStream 是 InputStream 的子类,因此将 FileInputStream 实例赋值给 InputStream 变量(inputstream 变量)是安全的。
InputStream 的 read() 方法返回一个 int,其中包含所读取字节的字节值。下面是一个 InputStream read() 示例:
int data = inputstream.read();
要读取 Java InputStream 中的所有字节,必须持续读取,直到返回值-1。该值表示已没有字节可从 InputStream 中读取。下面是一个从 Java InputStream 中读取所有字节的示例:
int data = inputStream.read(); while(data != -1) { // 用 data 变量做点什么 data = inputStream.read(); // 读取下一个字节 }
InputStream 的子类可能有其他 read() 方法。例如,DataInputStream 允许通过相应的 readBoolean() 和 readDouble() 方法读取 int、long、float、double、boolean 等 Java 基元数据。
如果 read() 方法返回-1,则表示已到达流的终点,即 InputStream 中已无数据可读。也就是说,-1 表示 int 值,而不是-1 表示字节或短值。这里有一个区别!
当数据流结束时,可以关闭 InputStream。
InputStream 类还包含两个 read() 方法,可将数据从 InputStream 源读入字节数组。这些方法是:
int read(byte[])
int read(byte[], int offset, int length)
read(byte[]) 方法将尝试向作为参数给定的字节数组中读取数组所能容纳的字节数。read(byte[]) 方法会返回一个 int,说明实际读取的字节数。如果从 InputStream 读取的字节数少于字节数组的容量,字节数组的其余部分将包含与读取开始前相同的数据。记住要检查返回的 int,以查看字节数组中实际读入了多少字节。
read(byte[], int offset, int length) 方法也是向字节数组中读取字节,但从数组的偏移字节开始,并从该位置向数组读取最大长度的字节。同样,read(byte[], int offset, int length) 方法会返回一个 int 值,说明实际读入数组的字节数,因此在处理读入的字节之前,请记住检查该值。
对于这两种方法,如果已到达流的末端,该方法将返回-1 作为已读取的字节数。
下面是一个使用 InputStream 的 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();
首先,该示例创建了一个字节数组。然后创建一个名为 bytesRead 的 int 变量,用于保存每次调用 read(byte[]) 时读取的字节数,并立即将第一次调用 read(byte[]) 时返回的值赋值给 bytesRead。
在 while 循环内部,doSomethingWithData() 方法会被调用,并将数据字节数组以及读入数组的字节数作为参数传递。在 while 循环结束时,数据再次被读入字节数组。
不需要太多想象力,就能知道如何使用 read(byte[],int offset, int length) 方法代替 read(byte[])。你只需将 read(byte[], int offset, int length) 调用替换为 read(byte[]) 调用即可。
Java InputStream 类包含一个名为 readAllBytes() 的方法(自 Java 9 起)。该方法读取 InputStream 中的所有字节,并返回一个包含这些字节的字节数组。如果你需要通过 FileInputStream 将文件中的所有字节读入字节数组,该方法将非常有用。下面是一个通过 readAllBytes() 从 Java InputStream 读取所有字节的示例:
byte[] fileBytes = null; try(InputStream input = new FileInputStream("myfile.txt")) { fileBytes = input.readALlBytes(); }
一次读取一个字节数组比一次从 Java InputStream 读取一个字节更快。通过读取字节数组而不是一次读取单个字节,在性能提升方面很容易达到 10 倍或更多。
具体的速度提升取决于所读取的字节数组的大小,以及运行代码的计算机的操作系统、硬件等。在做出决定之前,你应该研究一下目标系统的硬盘缓冲区大小等。不过,8KB 及以上的缓冲区大小会带来不错的速度提升。但是,一旦你的字节阵列超过了底层操作系统和硬件的容量,你就无法从更大的字节阵列中获得更快的速度。
你可能需要尝试使用不同的字节阵列大小并测量读取性能,以找到最佳的字节阵列大小。
你可以使用 Java BufferedInputStream 从 InputStream 中添加透明、自动读取和缓冲字节数组的功能。BufferedInputStream 会从底层 InputStream 中读取一大块字节到字节数组中。然后,你可以从 BufferedInputStream 中一个一个地读取字节,这样仍然可以获得读取字节数组而不是一次读取一个字节所带来的速度提升。下面是一个用 BufferedInputStream 封装 Java InputStream 的示例:
InputStream input = new BufferedInputStream( new FileInputStream("c:\\data\\input-file.txt"), 1024 * 1024 /* buffer size */);
请注意,BufferedInputStream 是 InputStream 的子类,可以在任何可以使用 InputStream 的地方使用。
InputStream 类有两个名为 mark() 和 reset() 的方法,InputStream 的子类可能支持也可能不支持这两个方法。
如果某个 InputStream 子类支持 mark() 和 reset() 方法,那么该子类应重载 markSupported() 方法,使其返回 true。如果 markSupported() 方法返回 false,则不支持 mark() 和 reset()。
mark() 在 InputStream 内部设置了一个标记,用于标示迄今为止已读取数据的流中点。这样,使用 InputStream 的代码就可以继续从中读取数据。如果使用 InputStream 的代码想返回到设置标记的数据流中的点,代码就会调用 InputStream 上的 reset()。然后,InputStream 就会 “倒带”,回到标记处,并重新开始从该点返回(读取)数据。当然,这将导致某些数据从 InputStream 返回不止一次。
mark() 和 reset() 方法通常用于实现解析器。有时,解析器可能需要提前读取 InputStream 中的数据,如果解析器没有找到它所期望的数据,它可能需要回退并尝试将读取的数据与其他数据进行匹配。
使用完 Java InputStream 后,必须将其关闭。关闭 InputStream 的方法是调用 InputStream close() 方法。下面是一个打开 InputStream、从中读取所有数据然后关闭它的示例:
InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt"); int data = inputstream.read(); while(data != -1) { data = inputstream.read(); } inputstream.close();
请注意 while 循环是如何持续进行的,直到从 InputStream read() 方法中读取 -1 值。之后,while 循环退出,并调用 InputStream close() 方法。
上述代码并不是 100% 健壮的。如果在从 InputStream 读取数据时出现异常,就永远不会调用 close() 方法。为了使代码更健壮,你必须使用 Java try-with-resources 结构。我的 Java IO 异常处理教程中也介绍了使用 Java IO 类时正确的异常处理方法。
下面是一个使用 try-with-resources 结构关闭 Java InputStream 的示例:
try( InputStream inputstream = new FileInputStream("file.txt") ) { int data = inputstream.read(); while(data != -1){ data = inputstream.read(); } }
请注意,InputStream 现在是在 try 关键字后的括号内声明的。这向 Java 发出信号,表明该 InputStream 将由 try-with-resources 结构管理。
一旦执行线程退出 try 代码块,就会关闭输入流变量。如果在 try 代码块内部抛出异常,则会捕获异常,关闭 InputStream,然后重新抛出异常。因此,当在 try-with-resources 代码块内使用时,可以保证 InputStream 已经关闭。
Java InputStream 是一种基于字节的数据流。如你所知,Java IO API 也有一套基于字符的输入流,称为 “阅读器”。你可以使用 Java InputStreamReader 将 Java InputStream 转换为 Java 阅读器。点击上一句中的链接,你可以阅读有关如何使用 InputStreamReader 的更多信息,下面是一个将 InputStream 转换为 InputStreamReader 的快速示例:
InputStream inputStream = new FileInputStream("c:\\data\\input.txt"); Reader inputStreamReader = new InputStreamReader(inputStream);