1. 概述
在本篇文章中,我们将深入探讨 Java 中的 OutputStream
类。OutputStream
是一个抽象类,它是所有表示字节输出流的类的父类。
随着内容的展开,我们会逐步解释“输出”和“流”的具体含义。
2. Java IO 简介
2.1. 输入与输出
Java IO 提供了一种机制,用于从数据源读取数据或将数据写入目标位置。其中,“输入”代表数据来源,“输出”则代表数据去向。
这些来源和目标可以是文件、管道、网络连接等。
2.2. 流(Streams)
Java IO 引入了“流”的概念,表示数据的连续流动过程。流支持多种类型的数据,如字节、字符、对象等。
同时,流也代表与数据源或目标的连接,因此分为 InputStream
和 OutputStream
两种类型。
3. OutputStream
实现的接口
OutputStream
实现了多个接口,这些接口赋予其子类不同的特性。我们快速过一遍。
3.1. Closeable
该接口提供了 close()
方法,用于关闭数据源或目标。每个 OutputStream
的实现都必须实现该方法,以便释放资源。
3.2. AutoCloseable
此接口也提供 close()
方法,行为与 Closeable
类似。不同的是,在 try-with-resources 语句块中,当退出时会自动调用 close()
方法。
更多关于 try-with-resources 的细节可以参考 Java Try-With-Resources。
3.3. Flushable
该接口提供 flush()
方法,用于将缓冲区的数据立即写入目标。
某些 OutputStream
实现可能会缓存数据以优化性能,而调用 flush()
可强制将缓存数据写入目标。
4. OutputStream
的核心方法
除了从 Closeable
和 Flushable
继承的 close()
和 flush()
外,OutputStream
还定义了几个关键方法。
4.1. write(int b)
用于向输出流中写入一个字节。虽然参数是 int
(4 字节),但只有最低位的一个字节会被写入,其余高位会被忽略:
public static void fileOutputStreamByteSingle(String file, String data) throws IOException {
byte[] bytes = data.getBytes();
try (OutputStream out = new FileOutputStream(file)) {
out.write(bytes[6]);
}
}
如果传入 "Hello World!"
,输出文件内容将是:
W
这对应字符串中索引为 6 的字符。
4.2. write(byte[] b, int off, int length)
该方法用于写入字节数组的一个子序列。
参数含义如下:
b
:字节数组off
:起始偏移量length
:要写入的长度
示例代码如下:
public static void fileOutputStreamByteSubSequence(
String file, String data) throws IOException {
byte[] bytes = data.getBytes();
try (OutputStream out = new FileOutputStream(file)) {
out.write(bytes, 6, 5);
}
}
如果传入 "Hello World!"
,输出文件内容将是:
World
即从索引 6 开始的 5 个字符。
4.3. write(byte[] b)
这是另一个重载版本,用于写入整个字节数组:
public static void fileOutputStreamByteSequence(String file, String data) throws IOException {
byte[] bytes = data.getBytes();
try (OutputStream out = new FileOutputStream(file)) {
out.write(bytes);
}
}
如果传入 "Hello World!"
,输出文件将包含:
Hello World!
这等价于调用 write(b, 0, b.length)
。
5. OutputStream
的常见子类
下面是一些常见的 OutputStream
子类,它们各自处理不同类型的输出目标。
我们只做简要介绍,不深入细节。
5.1. FileOutputStream
顾名思义,FileOutputStream
是用于将数据写入文件的输出流。它支持写入原始字节流。
我们前面的示例中已经演示过它的使用。
5.2. ByteArrayOutputStream
该类用于将数据写入内存中的字节数组。其内部缓冲区会随着写入数据自动增长。
默认初始大小为 32 字节,也可以通过构造函数指定。
⚠️ 注意:close()
方法对其几乎没有影响,即使关闭后仍可继续使用。
5.3. FilterOutputStream
它是一个抽象类,用于对输出数据进行某种转换后再写入目标。
FilterOutputStream
通常包装一个已有的 OutputStream
,并在此基础上添加功能。
常见的子类包括:
BufferedOutputStream
CheckedOutputStream
CipherOutputStream
DataOutputStream
DeflaterOutputStream
DigestOutputStream
InflaterOutputStream
PrintStream
5.4. ObjectOutputStream
用于将 Java 对象和基本数据类型写入输出流。需要对象实现 Serializable
接口。
可以通过包装一个 OutputStream
来指定输出目标,如文件。
更多关于序列化的细节请参考 Java 序列化。
5.5. PipedOutputStream
用于创建管道通信。PipedOutputStream
写入的数据可以被连接的 PipedInputStream
读取。
可通过构造函数或 connect()
方法将其与 PipedInputStream
关联。
6. 缓冲输出流(BufferedOutputStream)
IO 操作如磁盘读写、网络传输等通常比较耗时。频繁调用会显著影响性能。
Java 提供了缓冲流来优化这一问题。BufferedOutputStream
将数据先写入缓冲区,当缓冲区满或调用 flush()
时才真正写入目标。
示例代码如下:
public static void bufferedOutputStream(
String file, String ...data) throws IOException {
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
for(String s : data) {
out.write(s.getBytes());
out.write(" ".getBytes());
}
}
}
✅ 调用 write()
时只是写入缓冲区,真正写入文件是在 close()
时发生。
如果传入 "Hello"
, "World!"
,输出文件内容为:
Hello World!
7. 使用 OutputStreamWriter
写入文本
虽然字节流适合处理原始数据,但当我们需要写入文本时,手动转换字符到字节数组并不优雅。
Java 提供了 OutputStreamWriter
来简化这一过程。它包装一个 OutputStream
,并支持直接写入字符。
还可以指定字符编码,如 UTF-8:
public static void outputStreamWriter(String file, String data) throws IOException {
try (OutputStream out = new FileOutputStream(file);
Writer writer = new OutputStreamWriter(out,"UTF-8")) {
writer.write(data);
}
}
✅ 无需手动转换为字节数组,OutputStreamWriter
自动处理。
如果传入 "Hello World!"
,输出文件内容为:
Hello World!
8. 总结
本文我们详细介绍了 Java 的抽象类 OutputStream
,包括其实现的接口、提供的方法以及常见的子类。
还探讨了缓冲机制和字符流的使用方式。
如需查看文中示例代码,请访问 GitHub 项目地址。