OutputStream 是 Java I/O(输入/输出)体系中最核心、最基础的类之一,理解它是进行所有字节流输出操作的关键。

OutputStream 是什么?
OutputStream 是一个抽象类,位于 java.io 包中,它代表了字节输出流的所有超类(或称基类),任何将字节信息写入到目标(如文件、网络连接、内存缓冲区等)的类,都会直接或间接地继承自 OutputStream。
你可以把它想象成一个“数据管道”的出口端,你的程序通过这个管道,将一串串的字节(byte)数据“流”出去。
核心概念:流
在理解 OutputStream 之前,必须先理解“流”(Stream)的概念。
- 流:是一种用于读写数据的抽象序列,它就像一条河,数据就像河里的水,从源头流向目的地,程序可以按顺序地读取或写入数据,而不需要关心数据的具体存储细节。
- 字节流:以字节(
byte,8位)为单位进行数据传输,它适用于处理所有类型的数据,特别是二进制数据,如图片、音频、视频、可执行文件等。OutputStream就是字节流的输出端。
OutputStream 的核心方法
作为抽象类,OutputStream 定义了一些所有子类都必须实现的基本方法。

1 最核心的方法:write()
这是 OutputStream 最核心的方法,用于写入数据,它有三个重载版本:
-
write(int b)- 功能:写入一个字节的数据。
- 参数:
int b,注意,虽然是int类型,但它只写入最低的 8 位(一个字节),高 24 位会被忽略。 - 示例:
outputStream.write(65);// 写入字母 'A' 的 ASCII 码
-
write(byte[] b)- 功能:将一个字节数组
b中的所有字节一次性写入到输出流中。 - 参数:
byte[] b,要写入的字节数组。 - 示例:
byte[] data = "Hello".getBytes(); outputStream.write(data);
- 功能:将一个字节数组
-
write(byte[] b, int off, int len)
(图片来源网络,侵删)- 功能:将字节数组
b中从off位置开始的len个字节写入到输出流中,这是最灵活、最高效的方式。 - 参数:
byte[] b: 源字节数组。int off: 数组中开始读取的位置(偏移量)。int len: 要写入的字节长度。
- 示例:
byte[] data = "Hello, World!".getBytes(); outputStream.write(data, 7, 5);// 写入 "World"
- 功能:将字节数组
2 其他重要方法
-
flush()- 功能:刷新此输出流,并强制将所有缓冲的输出字节写入到目标设备中。
- 为什么需要? 为了提高性能,很多
OutputStream的实现类(如BufferedOutputStream)都会使用内部缓冲区,数据会先暂存在缓冲区,直到缓冲区满了或者调用flush()时,才会真正写入到文件或网络,如果不调用flush(),可能会导致数据丢失(尤其是在程序结束时缓冲区还有数据未写入)。 - 最佳实践:在写入操作完成后,或者在需要确保数据被立即发送时,务必调用
flush()。
-
close()- 功能:关闭此输出流并释放与此流相关联的任何系统资源(如文件句柄、网络连接等)。
- 为什么需要? 系统资源是有限的,用完必须释放,否则会导致资源泄露。
- 最佳实践:
close()方法在关闭流之前,会自动调用flush()方法,通常你只需要在finally块中调用close()即可。
常用的 OutputStream 子类
OutputStream 本身不能直接使用,我们需要使用它的子类来进行具体的操作。
| 子类名称 | 功能描述 | 常见使用场景 |
|---|---|---|
FileOutputStream |
将数据写入到文件中。 | 创建文件、写入日志、保存配置等。 |
ByteArrayOutputStream |
将数据写入到内存中的字节数组。 | 在内存中构建数据,之后再一次性写入文件或网络。 |
BufferedOutputStream |
为另一个输出流添加缓冲功能。 | 提高文件或网络写入性能,减少 I/O 操作次数。(装饰器模式) |
DataOutputStream |
允许以与机器无关的方式将 Java 基本数据类型写入输出流。 | 序列化数据,写入结构化文件(如自定义格式)。(装饰器模式) |
ObjectOutputStream |
将 Java 对象进行序列化并写入输出流。 | 对象持久化(保存对象状态)、远程方法调用。 |
SocketOutputStream |
网络编程中,通过套接字将数据发送到另一台计算机。 | 客户端/服务器通信。 |
代码示例
下面通过几个例子来演示如何使用 OutputStream。
示例 1:使用 FileOutputStream 写入文本文件
这是最常见的用法。注意:FileOutputStream 默认会覆盖文件原有内容。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class FileOutputStreamExample {
public static void main(String[] args) {
// 定义要写入的文件路径
String filePath = "example.txt";
// 定义要写入的内容
String content = "Hello, OutputStream!";
// try-with-resources 语句 (Java 7+),可以自动关闭资源,非常推荐!
try (OutputStream os = new FileOutputStream(filePath)) {
// 将字符串转换为字节数组
byte[] bytes = content.getBytes();
// 写入字节数组
os.write(bytes);
// os.flush(); // 在 try-with-resources 中,close() 会自动调用 flush(),所以这里可以省略
System.out.println("文件写入成功!");
} catch (IOException e) {
System.err.println("写入文件时发生错误: " + e.getMessage());
e.printStackTrace();
}
}
}
执行后,会在项目根目录下创建 example.txt 文件,内容为 "Hello, OutputStream!"。
示例 2:使用 BufferedOutputStream 提高写入性能
当写入大量数据时,使用缓冲流可以显著提高性能。
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class BufferedOutputStreamExample {
public static void main(String[] args) {
String filePath = "large_file.txt";
try (OutputStream fos = new FileOutputStream(filePath);
// BufferedOutputStream 包装了 FileOutputStream,为其添加缓冲功能
BufferedOutputStream bos = new BufferedOutputStream(fos))
{
for (int i = 0; i < 100000; i++) {
bos.write(("这是第 " + i + " 行数据\n").getBytes());
}
System.out.println("大文件写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个例子中,我们并没有直接调用 fos.write(),而是通过 bos 来写,数据会先进入 BufferedOutputStream 的缓冲区,缓冲区满了之后才会一次性写入到 FileOutputStream,最后再到文件,这大大减少了磁盘 I/O 操作的次数。
示例 3:追加内容到文件
FileOutputStream 的构造函数可以接受一个 boolean 参数,来决定是覆盖还是追加。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class AppendToFileExample {
public static void main(String[] args) {
String filePath = "example.txt";
String newContent = "\n这是追加的新内容。";
try (OutputStream os = new FileOutputStream(filePath, true)) { // true 表示追加模式
os.write(newContent.getBytes());
System.out.println("内容追加成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行后,example.txt 的内容会变成:
Hello, OutputStream!
这是追加的新内容。
OutputStream vs. Writer
这是一个非常常见且重要的区别。
| 特性 | OutputStream |
Writer |
|---|---|---|
| 数据单位 | 字节 (byte) |
字符 (char) |
| 处理数据 | 原始二进制数据,不进行任何编码转换。 | 字符数据,会自动处理字符编码(如 UTF-8, GBK)。 |
| 适用场景 | 图片、音频、视频、网络传输等所有二进制数据。 | 文本文件,特别是处理多语言文本时。 |
| 抽象基类 | java.io.OutputStream |
java.io.Writer |
| 对应文件类 | FileOutputStream |
FileWriter |
| 对应缓冲类 | BufferedOutputStream |
BufferedWriter |
简单总结:
- 如果你要处理纯文本,并且关心编码问题,请使用
Writer及其子类(如FileWriter,BufferedWriter)。 - 如果你要处理二进制数据(任何非文本的东西),或者你希望对字节数据进行底层操作,请使用
OutputStream及其子类。
最佳实践
-
总是使用
try-with-resources:从 Java 7 开始,推荐使用try-with-resources语句,它能自动实现AutoCloseable接口,在try块执行完毕后自动关闭资源,即使在try块中发生了异常,这能确保资源被正确释放,避免了资源泄露和代码臃肿的finally块。// 推荐 try (OutputStream os = new FileOutputStream("file.txt")) { // ... } catch (IOException e) { // ... } // 不推荐 (旧式写法) OutputStream os = null; try { os = new FileOutputStream("file.txt"); // ... } catch (IOException e) { // ... } finally { if (os != null) { try { os.close(); } catch (IOException e) { // ... } } } -
及时刷新和关闭:虽然
close()会自动flush(),但在某些需要立即看到结果的场景(如网络通信),在close()之前显式调用flush()是一个好习惯。 -
为频繁操作使用缓冲:当进行大量的、小批量的写入操作时,务必使用
BufferedOutputStream进行包装,以获得最佳性能。
希望这份详细的讲解能帮助你彻底理解 Java 的 OutputStream!
