Java FileInputStream 类(java.io.FileInputStream)可将文件内容作为字节流读取。Java FileInputStream 类是 Java InputStream 的子类。这意味着你可以将 Java FileInputStream 作为 InputStream 使用(FileInputStream 的行为类似于 InputStream)。
下面是一个简单的 FileInputStream 示例:
InputStream input = new FileInputStream("c:\\data\\input-text.txt"); int data = input.read(); while(data != -1) { //do something with data... doSomethingWithData(data); data = input.read(); } input.close();
注:为清晰起见,此处略去了正确的异常处理。要了解有关正确异常处理的更多信息,请访问 Java IO 异常处理。
还要注意的是,由于 FileInputStream 是 InputStream 的子类,因此我们可以将创建的 FileInputStream 转换为 InputStream,如上例所示。
FileInputStream 类有三种不同的构造函数,可以用来创建 FileInputStream 实例。这里我将介绍前两个构造函数。
第一个构造函数将一个字符串作为参数。该字符串应包含要读取文件所在文件系统的路径。下面是一个代码示例:
String path = "C:\\user\\data\\thefile.txt"; FileInputStream fileInputStream = new FileInputStream(path);
请注意路径字符串。它需要双反斜线(\\)才能在字符串中创建单反斜线,因为反斜线是 Java 字符串中的转义字符。要获得单反斜线,需要使用转义序列 (\\)。
在 unix 上,文件路径可能是这样的:
String path = "/home/hxstrive/data/thefile.txt";
请注意,我们使用了 “for-slash”(普通斜线字符)作为目录分隔符。这就是在 unix 上编写文件路径的方法。实际上,根据我的经验,如果在 Windows 下使用 / 作为目录分隔符(例如 c:/user/data/thefile.txt),Java 也能理解,但不要相信我的话。请在自己的系统上进行测试!
第二个 FileInputStream 构造函数的参数是一个 File 对象。File 对象必须指向要读取的文件。下面是一个例子:
String path = "C:\\data\\thefile.txt"; File file = new File(path); FileInputStream fileInputStream = new FileInputStream(file);
使用哪个构造函数取决于打开 FileInputStream 之前路径的形式。如果你已经有了一个字符串或文件,那就照原样使用。先将字符串转换为文件,或先将文件转换为字符串并没有什么特别的好处。
FileInputStream 的 read() 方法会返回一个 int,其中包含所读取字节的字节值。如果 read() 方法返回-1,则表示 FileInputStream 已无数据可读,可以关闭。也就是说,-1 表示 int 值,而不是-1 表示字节值。这里有一个区别!
使用 read() 方法就像使用 anInputStream 的 read()方法一样。下面是一个读取 Java FileInputStream 中所有数据的示例:
FileInputStream fileInputStream = new FileInputStream("c:\\data\\input-text.txt"); int data = fileInputStream.read(); while(data != -1) { // do something with data variable data = fileInputStream.read(); // read next byte }
当 while 循环结束时,所有字节都已从 FileInputStream 读取完毕。
作为一个 InputStream,FileInputStream 还有两个 read() 方法,可以将数据读入字节数组。顺便说一下,这些方法是从 Java InputStream 类继承而来的。这些方法是:
int read(byte[])
int read(byte[], int offset, int length)
第一个方法尝试用来自 FileInputStream 的字节填充作为参数传递给它的字节数组。
第二个方法尝试从字节数组中的单元偏移量开始向字节数组中读取长度字节,并从此处向前填充。
这两种方法都会返回实际读入字节数组的字节数。如果要读取的字节数少于数组的空间,或少于长度参数中指定的长度,读入字节数组的字节数就会减少。如果已从 FileInputStream 读取了所有字节,这些 read() 方法将返回-1。因此,有必要检查这些 read() 方法调用返回的值。
下面是一个调用 read(byte[]) 方法的示例:
FileInputStream fileInputStream = new FileInputStream("c:\\data\\input-text.txt"); byte[] data = new byte[1024]; int bytesRead = fileInputStream.read(data, 0, data.length); while(bytesRead != -1) { doSomethingWithData(data, bytesRead); bytesRead = fileInputStream.read(data, 0, data.length); }
请注意,read(data, 0, data.length) 等同于 read(data)。
为了简短起见,本示例中没有涉及 doSomethingWithData() 方法的实现。但是,它代表了你想对读取的数据执行的任何操作集。
一次读取一个字节数组比一次从 Java FileInputStream 读取一个字节更快。通过读取字节数组而不是一次读取单个字节,两者之间的性能差距可以轻松达到 10 倍或更多。
具体的速度提升取决于所读取字节数组的大小,以及运行代码的计算机的操作系统、硬件等。在做出决定之前,你应该研究一下目标系统的硬盘缓冲区大小等。不过,8KB 及以上的缓冲区大小会带来不错的速度提升。但是,一旦你的字节阵列超过了底层操作系统和硬件的容量,你就无法从更大的字节阵列中获得更快的速度。
你可能需要尝试使用不同的字节阵列大小并测量读取性能,以找到最佳的字节阵列大小。
你可以使用 Java BufferedInputStream 从 FileInputStream 中添加透明、自动读取和缓冲字节数组的功能。BufferedInputStream 会从底层 FileInputStream 中读取一大块字节到字节数组中。然后,你可以从 BufferedInputStream 中一个一个地读取字节,这样仍然可以大大提高读取字节数组而不是一次读取一个字节所带来的速度。下面是一个用 BufferedInputStream 封装 Java FileInputStream 的示例:
InputStream input = new BufferedInputStream( new FileInputStream("c:\\data\\input-file.txt"), 1024 * 1024 /* buffer size */);
请注意,BufferedInputStream 是 InputStream 的子类,可以在任何可以使用 InputStream 的地方使用。
完成从 Java FileInputStream 读取数据后,必须关闭它。关闭 FileInputStream 的方法是调用从 InputStream 继承而来的 close() 方法。下面是一个打开 FileInputStream、从中读取所有数据然后关闭它的示例:
FileInputStream fileInputStream = new FileInputStream("c:\\data\\input-text.txt"); int data = fileInputStream.read(); while(data != -1) { data = fileInputStream.read(); } fileInputStream.close();
请注意 while 循环是如何持续进行的,直到从 FileInputStream read() 方法中读取 -1 值。之后,while 循环退出,并调用 FileInputStream close() 方法。
上述代码并不是 100% 健壮的。如果在从 FileInputStream 读取数据时出现异常,就永远不会调用 close() 方法。为了使代码更健壮,你必须使用 Java 的 try with resources 结构。我的 Java IO 异常处理教程中也介绍了使用 Java IO 类的正确异常处理方法。
下面是一个使用 try-with-resources 结构关闭 Java FileInputStream 的示例:
try( FileInputStream fileInputStream = new FileInputStream("file.txt") ) { int data = fileInputStream.read(); while(data != -1){ data = fileInputStream.read(); } }
请注意,FileInputStream 是在 try 关键字后的括号内声明的。这向 Java 发出信号,表示该 FileInputStream 将由 try-with-resources 结构管理。
一旦执行线程退出 try 代码块,FileInputStream 即被关闭。如果在 try 代码块内部抛出异常,则会捕获异常,关闭 FileInputStream,然后重新抛出异常。因此,当在 try-with-resources 代码块中使用 FileInputStream 时,可以保证 FileInputStream 已被关闭。
Java FileInputStream 是一种基于字节的数据流。如你所知,Java IO API 也有一套基于字符的输入流,称为 “阅读器”。你可以使用 Java InputStreamReader 将 Java FileInputStream 转换为 Java 阅读器。点击上一句中的链接,你可以阅读有关如何使用 InputStreamReader 的更多信息,下面是一个将 Java FileInputStream 转换为 InputStreamReader 的快速示例:
InputStream inputStream = new FileInputStream("c:\\data\\input.txt"); Reader inputStreamReader = new InputStreamReader(inputStream);