一、BufferedOutputStream的介绍
BufferedOutputStream是缓冲字节输出流,继承自FilterOutputStream,它通过在内部创建一个缓冲区缓存写入底层输出流的字节数据,每次向底层字节输出流写入数据时不是立即写入而是先写入到缓冲区等到缓冲区已满或者达到限定条件再将缓冲区中的字节数据真正写入底层字节输出流,这样可以避免每次写入都要进行一次底层调用,在没有数据实时写入需求的场景下可以提高性能;
二、BufferedOutputStream数据结构
public
class BufferedOutputStream extends FilterOutputStream {
/**
* 保存字节数据的内部缓冲区
*/
protected byte buf[];
/**
* 缓冲区中的有效字节数,在0~buf.length范围内,也可以视为缓冲区数组下一个字节的写入位置
*/
protected int count;
}
BufferedOutputStream继承自FilterOutputStream,基于装饰器模式可以使用它包装其他字节输出流为它们提供数据写入缓冲的功能,类内部的成员变量有包装的底层字节输出流对象out,内部缓冲区buf和统计缓冲区有效字节个数的变量count。
三、BufferedOutputStream源码分析
1 - 构造函数
/**
* 构造函数,指定底层字节输出流out,缓冲区数组长度默认8192
*/
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
/**
* 构造函数,指定底层字节输出流out,创建指定长度为size的缓冲区数组
*/
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
BufferedOutputStream的构造函数主要做了两件事:绑定一个底层字节输出流(真正负责写入的对象)和创建内部缓冲区
2 - void write(int b) - 写入单个字节
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
write(int b)方法写入单个字节数据。首先判断当前缓冲区是否写满如果已经写满,调用flushBuffer方法将缓冲区数据写入到底层字节输出流out中,重置缓冲区(count设置为0重复利用缓冲区写入),继续写入当前指定字节。
3 - 其他成员方法
/**
* 往流中写入指定字节数组b的一部分(b下标off开始长度len)
*/
public synchronized void write(byte b[], int off, int len) throws IOException {
//若写入的字节数大于缓冲区,刷新当前缓冲区,把缓冲区数据写入底层字节输入流,后续写入直接调用底层字节输出流进行写入
if (len >= buf.length) {
flushBuffer();
out.write(b, off, len);
return;
}
//若所有字节数据写入后缓冲区会溢出那么刷新当前缓冲区,把缓冲区数据写入底层字节输入流
if (len > buf.length - count) {
flushBuffer();
}
//将字节数据先写入当前缓冲区
System.arraycopy(b, off, buf, count, len);
//更新当前缓冲区有效字节个数count
count += len;
}
/**
* 刷新流,这个操作会强制将当前缓冲区中的字节数据写入底层字节输出流中
*/
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}