Java IO 流是你可以读取或写入的数据流。正如 Java IO 概述中提到的,数据流通常连接到数据源或数据目标,如文件或网络连接。
流不像数组那样有读取或写入数据的索引概念,通常也不能像数组或使用 RandomAccessFile 的文件那样在流中来回移动,数据流只是一个连续的数据流。
一些数据流实现(如 PushbackInputStream)允许将数据推回数据流,以便以后再次读取。但你只能推回有限的数据量,而且不能像数组那样随意遍历数据。数据只能按顺序访问。
Java IO 流通常基于字节(byte)或字符(char)。基于字节的流通常被称为 “流”,如 InputStream 或 OutputStream。除了 DataInputStream 和 DataOutputStream 能读写 int、long、float 和 double 值外,这些流一次只能读写一个原始字节。
基于字符的数据流通常称为“读取器”或“写入器”。基于字符的流可以读/写字符(如 Latin1 或 UNICODE 字符)。
java.io.InputStream 类是所有 Java IO 输入流的基类,如下图:
如果你正在编写一个需要从流中读取输入数据的组件,请尽量使我们的组件依赖 InputStream,而不是它的任何子类(如 FileInputStream)。这样做能让你的代码处理所有类型的输入流,而不是只处理具体的子类。
不过,仅依赖 InputStream 并不总是可行的。如果需要向流推回数据,就必须依赖于 PushbackInputStream,也就是说,你的流变量必须是这种类型。否则,你的代码将无法调用 PushbackInputStream 上的 unread() 方法。
从 InputStream 读取数据时,通常需要调用 read() 方法。read() 方法会返回一个 int,其中包含所读取字节的字节值。如果没有数据可读,read() 方法通常会返回-1;
示例:
package com.hxstrive.java_io.inputstream; import java.io.*; import java.util.Arrays; /** * 读取文件示例 * @author hxstrive.com */ public class FileInputStreamDemo2 { public static void main(String[] args) { try(FileInputStream input = new FileInputStream("D:\\input.txt"); BufferedInputStream inputBuffered = new BufferedInputStream(new FileInputStream("D:\\input.txt"))) { // 参数实际类型为 FileInputStream byte[] bytes = readFile(input); System.out.println(new String(bytes)); // Hello Java IO // 参数实际类型为 BufferedInputStream bytes = readFile(new BufferedInputStream(inputBuffered)); System.out.println(new String(bytes)); // Hello Java IO } catch (Exception e) { e.printStackTrace(); } } // 参数使用 InputStream 类型,可以读取任何输入流 public static byte[] readFile(InputStream input) throws IOException { byte[] buffer = new byte[input.available()]; int len = input.read(buffer); return Arrays.copyOf(buffer, len); } }
java.io.OutputStream 类是所有 Java IO 输出流的基类。如下图:
如果你正在编写一个需要将输出写入流的组件,请尽量确保该组件依赖 OutputStream,而不是它的某个具体子类。
示例:
package com.hxstrive.java_io.outputstream; import java.io.*; /** * 写文件示例 * @author hxstrive.com */ public class FileOutputStreamDemo2 { public static void main(String[] args) { File file1 = new File("D:\\output.txt"); File file2 = new File("D:\\output2.txt"); try(FileOutputStream output = new FileOutputStream(file1); BufferedOutputStream outputBuffered = new BufferedOutputStream(new FileOutputStream(file2)); FileInputStream input = new FileInputStream(file1); FileInputStream input2 = new FileInputStream(file2)) { // 写出数据 writeFile(output, "hello world 1"); writeFile(outputBuffered, "hello world 2"); // 读取数据,验证结果 System.out.println(readFile(input)); // hello world 1 System.out.println(readFile(input2)); // hello world 2 } catch (Exception e) { e.printStackTrace(); } } // 写出数据,接受所有的输出流 public static void writeFile(OutputStream output, String content) throws IOException { output.write(content.getBytes()); output.flush(); // 强制刷新缓冲区,将数据写出 } // 读取文件内容,返回字符串 public static String readFile(InputStream input) throws IOException { byte[] buffer = new byte[input.available()]; int len = input.read(buffer); return new String(buffer, 0, len); } } 如果我们将上述代码 writeFile() 改为: // 写出数据,接受所有的输出流 public static void writeFile(FileOutputStream output, String content) throws IOException { output.write(content.getBytes()); output.flush(); // 强制刷新缓冲区,将数据写出 }
那么,writeFile(outputBuffered, "hello world 2") 语句就会出现类型错误。
你可以将流组合成链,实现更高级的输入和输出操作。例如,从文件中逐个字节读取数据的速度很慢。从磁盘读取一个较大的数据块,然后逐个字节遍历该数据块,速度会更快。要实现缓冲,可以将 InputStream 封装在 BufferedInputStream 中。
示例:
package com.hxstrive.java_io.inputstream; import java.io.*; import java.util.Arrays; /** * 读取文件示例 * @author hxstrive.com */ public class FileInputStreamDemo3 { public static void main(String[] args) throws Exception { File file = new File("D:\\input.txt"); // 文件输入流 FileInputStream fileInputStream = new FileInputStream(file); // 文件输入流读取慢,操作不方便,所以使用缓冲流 BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); byte[] bytes = new byte[1024]; int len; while ((len = bufferedInputStream.read(bytes)) != -1) { System.out.println(Arrays.toString(Arrays.copyOf(bytes, len))); //[72, 101, 108, 108, 111, 32, 74, 97, 118, 97, 32, 73, 79] } // 关闭流,释放资源,不要忘了 // 如果使用 try-with-resources 语句,将自动释放,不需手动操作 fileInputStream.close(); bufferedInputStream.close(); } }
缓冲也可应用于 OutputStream,从而将写入磁盘(或底层流)的数据分批写入较大的块。这也能提供更快的输出。这可以通过缓冲输出流来实现。
缓冲只是通过组合流实现的效果之一。你还可以将 InputStream 包裹在 PushbackStream 中。这样,你就可以将数据推回流中,以便稍后重新读取。这有时在解析过程中非常方便。或者,你也可以使用 SequenceInputStream 将两个 InputStream 合二为一。
将输入流和输出流组合成链还可以实现其他一些效果。你甚至可以编写自己的流类来封装 Java 自带的标准流类。这样,你就可以创建自己的效果或过滤器。