Java IO 流是你可以读取或写入的数据流。正如 Java IO:简介中提到的,数据流通常连接到数据源或数据目标,如文件或网络连接。如下图:

流不像数组那样有读取或写入数据的索引概念,通常也不能像数组或使用 RandomAccessFile 的文件那样在流中来回移动,数据流只是一个连续的数据流。如下图:

上图中,我们将流想象成一根装满数据到管子,输入端将数据一个一个的放入到数据流中,输出流则从数据流中逐一读取数据。
一些数据流实现(如 PushbackInputStream)允许将数据推回数据流,以便以后再次读取。但你只能推回有限的数据量,而且不能像数组那样随意遍历数据。数据只能按顺序访问。
Java IO 流通常基于字节(byte)或字符(char):
基于字节:数据流通常被称为“Stream”,如 InputStream 或 OutputStream 类。注意,除了 DataInputStream 和 DataOutputStream 类能读写 int、long、float 和 double 值外,这些流一次只能读写一个原始字节。
基于字符:数据流通常被称为“Reader”或“Writer”,字符流可以读/写字符(如 Latin1 或 UNICODE 字符)。
java.io.InputStream 类是所有 Java IO 输入流的基类,如下图:

如果你正在编写一个需要从流中读取输入数据的组件,请尽量使我们的组件依赖 InputStream,而不是它的任何子类(如 FileInputStream)。这样做能让你的代码能够处理所有类型的输入流,而不是只处理具体的子类。
不过,仅依赖 InputStream 并不总是可行的。如果需要向流推回数据,就必须依赖于 PushbackInputStream,也就是说,你的流变量必须是这种类型。否则,你的代码将无法调用 PushbackInputStream 上的 unread() 方法(因为 unread() 方法是 PushbackInputStream 特有的,InputStream 类中没有该方法)。
从 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 自带的标准流类。这样,你就可以创建自己的效果或过滤器。