Jakob Jenkov 2020-03-25
Java 的 OutputStream 类(java.io.OutputStream)是 Java IO API 中所有输出流的基类。OutputStream 的子类包括 Java BufferedOutputStream、Java FileOutputStream 等。完整的输出流列表可参见 Java IO 页面底部的表格。
Java OutputStream 的子类
以下是 Java OutputStream 类的一些常见子类:
- ByteArrayOutputStream
- FileOutputStream
- PipedOutputStream
- BufferedOutputStream
- FilterOutputStream
- DataOutputStream
- PrintStream
- ObjectOutputStream
OutputStream 与目标
Java 的 OutputStream 通常连接到某个数据目标——如 Java IO 概述 所述,例如文件、网络连接、管道、内存缓冲区等。写入 OutputStream 的所有数据最终都会被发送到该目标。
write(byte)
write(byte) 方法用于向 Java OutputStream 写入单个字节。OutputStream 的 write() 方法接收一个 int 类型参数,其中包含要写入字节的值。只有该 int 值的最低有效字节(第一个字节)会被写入,其余部分将被忽略。
某些 OutputStream 的子类可能提供其他 write() 方法。例如,DataOutputStream 允许你使用对应的 writeBoolean()、writeDouble() 等方法写入 Java 基本类型(如 int、long、float、double、boolean 等)。
以下是一个 OutputStream.write() 的示例:
OutputStream outputStream = new FileOutputStream("c:\\data\\output-text.txt");
while(hasMoreData()) {
int data = getMoreData();
outputStream.write(data);
}
outputStream.close();
此示例首先创建一个 FileOutputStream 作为数据写入的目标。然后进入一个 while 循环,退出条件由 hasMoreData() 方法返回值决定(此处未展示其实现,可理解为:有更多数据时返回 true,否则返回 false)。
在循环内部,调用 getMoreData() 获取下一个要写入的数据,并通过 write() 方法写入 OutputStream。当 hasMoreData() 返回 false 时,循环结束。
注意:为清晰起见,此处省略了正确的异常处理。有关异常处理的详细信息,请参阅 Java IO 异常处理。
write(byte[])
Java OutputStream 提供了两种方法,可以一次写入一个字节数组:
write(byte[] bytes)write(byte[] bytes, int offset, int length)
write(byte[] bytes) 方法会将整个字节数组中的所有字节写入 OutputStream。
write(byte[] bytes, int offset, int length) 方法则从字节数组的 offset 索引开始,写入 length 个字节到 OutputStream。
这两个方法都会返回实际写入的字节数(如果未能按请求写入全部字节或指定长度的字节)。
以下是一个使用第二种方法写入字节数组的示例:
OutputStream outputStream = new FileOutputStream("/usr/home/jakobjenkov/output.txt");
byte[] sourceBytes = ... // 从某处获取源字节数组
int bytesWritten = outputStream.write(sourceBytes, 0, sourceBytes.length);
此示例尝试将 sourceBytes 数组中的所有字节写入 OutputStream。执行后,bytesWritten 变量将包含实际写入的字节数。
写入性能
一次性写入字节数组比逐字节写入要快得多,速度提升可能高达 10 倍甚至更多。因此,建议尽可能使用 write(byte[]) 方法。
具体性能提升取决于运行 Java 代码的底层操作系统和硬件,例如内存速度、硬盘速度与缓冲区大小,或者网卡速度与缓冲区大小(取决于 OutputStream 的目标类型)。
通过 BufferedOutputStream 实现透明缓冲
你可以通过将 OutputStream 包装在 Java BufferedOutputStream 中,实现对写入字节的透明缓冲。所有写入 BufferedOutputStream 的字节会先被缓存在其内部的字节数组中;当缓冲区满时,才会一次性刷新(flush)到底层的 OutputStream。
示例:
int bufferSize = 8 * 1024;
OutputStream outputStream = new BufferedOutputStream(
new FileOutputStream("c:\\data\\output-file.txt"),
bufferSize
);
flush()
Java OutputStream 的 flush() 方法会将所有已写入但尚未发送到目标的数据强制刷新到底层数据目标。例如,若 OutputStream 是 FileOutputStream,写入的数据可能仍缓存在操作系统内存中,尚未真正写入磁盘。调用 flush() 可确保这些缓冲数据被立即写入磁盘(或网络等目标)。
示例:
outputStream.flush();
关闭 OutputStream
完成数据写入后,应关闭 OutputStream。可通过调用其 close() 方法实现:
OutputStream outputStream = new FileOutputStream("c:\\data\\output-text.txt");
while(hasMoreData()) {
int data = getMoreData();
outputStream.write(data);
}
outputStream.close();
然而,上述代码不够健壮:如果 write() 抛出异常,close() 将不会被执行,导致资源泄漏。
推荐使用 try-with-resources 语句自动关闭流:
try (OutputStream outputStream = new FileOutputStream("c:\\data\\output-text.txt")) {
while(hasMoreData()) {
int data = getMoreData();
outputStream.write(data);
}
}
当 try 块退出时(无论正常结束还是抛出异常),OutputStream 的 close() 方法都会被自动调用。
将 OutputStream 转换为 Writer
Java OutputStream 是基于字节的流。若需处理字符,可使用 Java OutputStreamWriter 将其转换为基于字符的 Writer。
示例:
OutputStream outputStream = new FileOutputStream("c:\\data\\output.txt");
Writer outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.write("Hello World");