跳到主要内容

Java BufferedOutputStream

在Java IO操作中,效率是一个重要的考量因素。当我们需要向文件或其他目标写入大量数据时,如果每次只写入少量字节,频繁地进行IO操作会导致程序性能下降。为了解决这个问题,Java提供了BufferedOutputStream类,它通过添加缓冲区来提高输出流的性能。

什么是BufferedOutputStream?

BufferedOutputStream是Java IO包中的一个类,它继承自FilterOutputStream。它的主要作用是为其他输出流添加缓冲功能,从而提高IO操作的效率。

当我们向BufferedOutputStream写入数据时,数据首先被写入内存中的缓冲区。只有当缓冲区满了,或者调用flush()方法时,缓冲区中的数据才会被一次性写入底层输出流。这样就减少了实际的IO操作次数,提高了程序的性能。

BufferedOutputStream的构造方法

BufferedOutputStream类提供了两个构造方法:

java
// 创建一个新的缓冲输出流,以将数据写入指定的底层输出流,使用默认缓冲区大小(8192字节)
public BufferedOutputStream(OutputStream out)

// 创建一个新的缓冲输出流,以将数据写入指定的底层输出流,使用指定的缓冲区大小
public BufferedOutputStream(OutputStream out, int size)

基本用法示例

下面是一个使用BufferedOutputStream写入文件的基本示例:

java
import java.io.*;

public class BufferedOutputStreamExample {
public static void main(String[] args) {
try {
// 创建一个文件输出流
FileOutputStream fileOut = new FileOutputStream("test.txt");

// 创建一个缓冲输出流,包装文件输出流
BufferedOutputStream buffOut = new BufferedOutputStream(fileOut);

// 要写入的字符串
String data = "Hello, BufferedOutputStream!";

// 将字符串转换为字节数组并写入缓冲输出流
buffOut.write(data.getBytes());

// 刷新缓冲区,确保所有数据都写入文件
buffOut.flush();

// 关闭流
buffOut.close();
fileOut.close();

System.out.println("数据已成功写入文件!");
} catch (IOException e) {
e.printStackTrace();
}
}
}

输出:

数据已成功写入文件!
提示

当你关闭BufferedOutputStream时,它会自动调用flush()方法将缓冲区中的所有数据写入底层输出流,然后关闭底层流。

BufferedOutputStream的主要方法

  1. write(int b): 将指定的字节写入缓冲输出流。

  2. write(byte[] b, int off, int len): 将指定字节数组中从偏移量off开始的len个字节写入缓冲输出流。

  3. flush(): 刷新缓冲输出流,将缓冲区中的所有数据写入底层输出流。

  4. close(): 关闭流,释放与之相关联的所有资源。

BufferedOutputStream与FileOutputStream性能对比

为了直观地展示BufferedOutputStream的性能优势,下面是一个将大量数据写入文件的性能对比示例:

java
import java.io.*;

public class OutputStreamPerformanceComparison {
public static void main(String[] args) {
try {
// 准备写入的数据量(约10MB)
byte[] data = new byte[10 * 1024 * 1024]; // 10MB的字节数组
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}

// 使用FileOutputStream(无缓冲)
long startTime = System.currentTimeMillis();
FileOutputStream fos = new FileOutputStream("unbuffered.dat");
for (int i = 0; i < data.length; i++) {
fos.write(data[i]);
}
fos.close();
long endTime = System.currentTimeMillis();
System.out.println("FileOutputStream写入时间: " + (endTime - startTime) + " 毫秒");

// 使用BufferedOutputStream(有缓冲)
startTime = System.currentTimeMillis();
FileOutputStream fos2 = new FileOutputStream("buffered.dat");
BufferedOutputStream bos = new BufferedOutputStream(fos2);
for (int i = 0; i < data.length; i++) {
bos.write(data[i]);
}
bos.close();
endTime = System.currentTimeMillis();
System.out.println("BufferedOutputStream写入时间: " + (endTime - startTime) + " 毫秒");

} catch (IOException e) {
e.printStackTrace();
}
}
}

可能的输出:

FileOutputStream写入时间: 28647 毫秒
BufferedOutputStream写入时间: 89 毫秒
备注

实际运行结果可能会因硬件配置和系统负载不同而有所差异,但BufferedOutputStream通常会比FileOutputStream快很多倍。

实际应用场景

1. 文件复制

当需要复制大文件时,使用缓冲流可以显著提高效率:

java
import java.io.*;

public class FileCopyWithBuffer {
public static void main(String[] args) {
try {
// 源文件和目标文件
File sourceFile = new File("source.jpg");
File targetFile = new File("target.jpg");

// 创建输入流和缓冲输入流
FileInputStream fis = new FileInputStream(sourceFile);
BufferedInputStream bis = new BufferedInputStream(fis);

// 创建输出流和缓冲输出流
FileOutputStream fos = new FileOutputStream(targetFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);

// 缓冲区
byte[] buffer = new byte[1024];
int bytesRead;

// 读取和写入数据
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}

// 关闭流
bis.close();
bos.close();

System.out.println("文件复制完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}

2. 网络数据传输

在网络编程中,使用缓冲流可以减少网络请求的次数,提高传输效率:

java
import java.io.*;
import java.net.Socket;

public class NetworkDataTransfer {
public static void sendDataToServer(String serverAddress, int port, byte[] data) {
try {
// 创建套接字连接到服务器
Socket socket = new Socket(serverAddress, port);

// 获取输出流并包装为缓冲输出流
OutputStream out = socket.getOutputStream();
BufferedOutputStream buffOut = new BufferedOutputStream(out);

// 写入数据
buffOut.write(data);

// 刷新并关闭流
buffOut.flush();
buffOut.close();
socket.close();

System.out.println("数据成功发送到服务器!");
} catch (IOException e) {
e.printStackTrace();
}
}
}

3. 创建ZIP文件

当创建ZIP文件时,使用缓冲输出流可以提高压缩速度:

java
import java.io.*;
import java.util.zip.*;

public class ZipCreator {
public static void createZipFile(File fileToZip, String zipFileName) {
try {
FileOutputStream fos = new FileOutputStream(zipFileName);
BufferedOutputStream bos = new BufferedOutputStream(fos);
ZipOutputStream zos = new ZipOutputStream(bos);

FileInputStream fis = new FileInputStream(fileToZip);
BufferedInputStream bis = new BufferedInputStream(fis);

// 添加ZIP条目
ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
zos.putNextEntry(zipEntry);

// 写入ZIP文件
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
zos.write(buffer, 0, bytesRead);
}

// 关闭流
zos.closeEntry();
bis.close();
zos.close();

System.out.println("ZIP文件创建成功: " + zipFileName);
} catch (IOException e) {
e.printStackTrace();
}
}
}

最佳实践

  1. 使用try-with-resources语句:自动关闭流,防止资源泄漏。
java
try (
FileOutputStream fos = new FileOutputStream("file.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos)
) {
bos.write("Hello World".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
  1. 选择合适的缓冲区大小:默认缓冲区大小(8KB)适合大多数情况,但对于特殊需求,可以调整缓冲区大小。

  2. 记得调用flush()方法:当需要立即写入数据时,不要忘记调用flush()方法。

  3. 关闭最外层的流:当使用多层流包装时,只需关闭最外层的流,内部的流会自动关闭。

总结

BufferedOutputStream是Java IO中一个重要的类,通过在内存中缓冲数据,减少实际的IO操作次数,从而显著提高输出性能。它特别适用于需要频繁写入数据的场景,如文件复制、网络数据传输等。

使用BufferedOutputStream时需要注意:

  • 确保在不需要流时正确关闭它
  • 在需要立即写入数据时调用flush()方法
  • 考虑使用try-with-resources语句自动管理资源

通过合理使用BufferedOutputStream,你可以有效提高Java应用程序的IO性能。

练习

  1. 编写一个程序,使用BufferedOutputStream将一个包含1000个随机整数的数组写入文件。

  2. 创建一个简单的日志系统,使用BufferedOutputStream将日志信息写入文件,并确保重要的日志信息立即写入(提示:使用flush()方法)。

  3. 比较使用BufferedOutputStream和不使用缓冲的情况下,写入10MB数据到文件的性能差异。

延伸阅读

  • Java官方文档中的BufferedOutputStream
  • 学习相关的BufferedInputStream类,了解如何提高读取效率
  • 探索Java NIO(New IO)包中的缓冲区概念,例如ByteBuffer