Java字节流InputStream和OutputStream详解(附带实例)
在 Java 中,I/O 流(输入/输出流)是用于进行数据输入和输出的机制。
I/O 流是 Java 程序与外部设备(如文件、网络、控制台等)进行数据交互的重要方式。通过 I/O 流,我们可以从外部设备读取数据到程序中,也可以将程序中的数据输出到外部设备。
Java 的 I/O 流以字节流和字符流两种方式进行数据的传输。其中,字节流以字节为单位进行数据传输,用于处理二进制数据或字节数据,适用于处理图片、音频、视频等文件。字节流主要由 InputStream 类和 OutputStream 类及其子类组成。

图 1 字节输入流类的继承层次
InputStream 有很多子类,这些类的说明如下表所示:
InputStream 定义了很多方法,影响着字节输入流的行为,主要方法如下:

图 2 字节输出流类的继承层次
OutputStream 有很多子类,这些类的说明如下表所示:
OutputStream 定义了很多方法,影响着字节输出流的行为,主要方法如下。
上述所有方法都声明了抛出异常 IOException,因此使用时要注意处理异常。
流(包括输入流和输出流)所占用的资源不能通过 Java 虚拟机的垃圾收集器回收,需要程序员自己释放:
FileInputStream 和 FileOutputStream 中的主要方法都继承自 InputStream 和 OutputStream。
FileInputStream 的构造方法如下:
2. FileOutputStream的构造方法
接下来实现将当前项目的 data 目录下的“漫画Java.png”文件内容复制为“漫画Java-副本.png”。参考代码如下:
I/O 流是 Java 程序与外部设备(如文件、网络、控制台等)进行数据交互的重要方式。通过 I/O 流,我们可以从外部设备读取数据到程序中,也可以将程序中的数据输出到外部设备。
Java 的 I/O 流以字节流和字符流两种方式进行数据的传输。其中,字节流以字节为单位进行数据传输,用于处理二进制数据或字节数据,适用于处理图片、音频、视频等文件。字节流主要由 InputStream 类和 OutputStream 类及其子类组成。
Java字节流
1) 字节输入流
字节输入流的根类是 InputStream,如下图所示。
图 1 字节输入流类的继承层次
InputStream 有很多子类,这些类的说明如下表所示:
类 | 描述 |
---|---|
FileInputStream | 文件输入流 |
ByteArrayInputStream | 面向字节数组的输入流 |
PipedInputStream | 管道输入流,用于两个线程之间的数据传递 |
FilterInputStream | 过滤输入流,它是一个装饰器,用于扩展其他输入流 |
BufferedInputStream | 缓冲区输入流,它是 FilterInputStream 的子类 |
DataInputStream | 面向基本数据类型的输入流 |
InputStream 定义了很多方法,影响着字节输入流的行为,主要方法如下:
- int read():读取一个字节,返回 0~255 范围内的 int 字节值。如果已经到达流末尾,而且没有可用的字节,则返回值-1。
- int read(byte b[ ]):读取多个字节,并将数据放到字节数组 b 中,返回值为实际读取的字节的数量。如果已经到达流末尾,而且没有可用的字节,则返回值 -1。
- int read(byte b[ ],int off,int len):最多读取 len 个字节,数据放到以索引 off 开始的字节数组 b 中,将读取的第一个字节存储在元素 b[off]中,下一个存储在 b[off+1]中,依次类推。返回值为实际读取的字节的数量。如果已经到达流
2) 字节输出流
字节输出流的根类是 OutputStream,如下图所示:
图 2 字节输出流类的继承层次
OutputStream 有很多子类,这些类的说明如下表所示:
类 | 描述 |
---|---|
FileOutputStream | 文件输出流 |
ByteArrayOutputStream | 面向字节数组的输出流 |
PipedOutputStream | 管道输出流,用于两个线程之间的数据传递 |
FilterOutputStream | 过滤输出流,它是一个装饰器,用于扩展其他输出流 |
BufferedOutputStream | 缓冲区输出流,它是 FilterOutputStream 的子类 |
DataOutputStream | 面向基本数据类型的输出流 |
OutputStream 定义了很多方法,影响着字节输出流的行为,主要方法如下。
- void write(int b):将 b 写入到输出流,b 是 int 类型,占有 32 位,写入过程是写入 b 的 8 个低位,b 的 24 个高位将被忽略。
- void write(byte b[]):将 b.length 个字节从指定字节数组 b 写入到输出流。
- void write(byte b[], int off, int len):把字节数组 b 中从下标 off 开始,长度为 len 的字节写入到输出流。
- void flush():刷空输出流,并输出所有被缓存的字节。由于某些流支持缓存功能,该方法将把缓存中的所有内容都强制输出到流中。
- void close():流操作完毕后必须关闭。
上述所有方法都声明了抛出异常 IOException,因此使用时要注意处理异常。
流(包括输入流和输出流)所占用的资源不能通过 Java 虚拟机的垃圾收集器回收,需要程序员自己释放:
- 一种方法是可以在 finally 代码块中调用 close() 方法关闭流,释放流所占用的资源。
- 另一种方法是通过自动资源管理技术管理这些流,流(包括输入流和输出流)都实现了 AutoCloseable 接口,可以使用自动资源管理技术。
Java字节流复制二进制文件
复制文件时数据源是文件,所以会用到文件输入流 FileInputStream;数据目的地也是文件,所以会用到文件输出流 FileOutputStream。FileInputStream 和 FileOutputStream 中的主要方法都继承自 InputStream 和 OutputStream。
FileInputStream 的构造方法如下:
- FileInputStream(String name):创建 FileInputStream 对象,name 是文件名。如果文件不存在,则抛出 FileNotFoundException 异常。
- FileInputStream(File file):通过 File 对象创建 FileInputStream 对象。如果文件不存在,则抛出 FileNotFoundException 异常。
2. FileOutputStream的构造方法
- FileOutputStream(String name):通过指定文件名 name 创建 FileOutputStream 对象。如果 name 文件存在,但如果是一个目录或文件无法打开,则抛出 FileNotFoundException 异常。
- FileOutputStream(String name, boolean append):通过指定文件名 name 创建 FileOutputStream 对象,如果 append 参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。如果 name 文件存在,但是一个目录或文件无法打开,则抛出 FileNotFoundException 异常。
- FileOutputStream(File file):通过 File 对象创建 FileOutputStream 对象。如果 file 文件存在,但是一个目录或文件无法打开,则抛出 FileNotFoundException 异常。
- FileOutputStream(File file, boolean append):通过 File 对象创建 FileOutputStream 对象,如果 append 参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。如果 file 文件存在,但是一个目录或文件无法打开,则抛出 FileNotFoundException 异常。
接下来实现将当前项目的 data 目录下的“漫画Java.png”文件内容复制为“漫画Java-副本.png”。参考代码如下:
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; // Main 类 public class BinaryFileCopier { public static void main(String args[]) { try (FileInputStream in = new FileInputStream("data/漫画 Java.png"); FileOutputStream out = new FileOutputStream("data/漫画 Java - 副本.png")) { // ① // 准备一个缓冲区 byte[] buffer = new byte[1024]; // ② // 首先读取一次 int len = in.read(buffer); // ③ while (len != -1) { // ④ // 开始写入数据 out.write(buffer, 0, len); // ⑤ // 再读取一次 len = in.read(buffer); // ⑥ } } catch (FileNotFoundException e) { System.out.println("复制失败! 文件没有发现!"); } catch (IOException e) { System.out.println("复制失败!"); } System.out.println("复制完成。"); } }若这段代码成功运行,会实现文件复制,下面解释一下代码:
- 上述代码第 ① 处创建 FileInputStream 和 FileOutputStream 对象,这是自动资源管理的写法,不需要自己关闭流。
- 代码第 ② 处是准备一个缓冲区,它是字节数组,读取输入流的数据并保存到缓冲区中,然后将缓冲区中的数据再写入到输出流中。
- 代码第 ③ 处是第一次从输入流中读取数据,数据保存到 buffer 中,len 是实际读取的字节数。
- 代码第 ④ 处判断读取的流是否到文件尾部。
- 代码第 ⑤ 处是将 buffer 中数据写入缓存区。
- 代码第 ⑥ 处再次读取数据,然后回到 while(len != -1)语句再判断是否读取到文件尾部。