一、ByteArrayOutputStream介绍
ByteArrayOutputStream是字节输出流的实现,字节数据写入到内部的缓冲字节数组。该缓冲字节数组随着数据写入而增大。写入其中的数据可通过toString()或者toByteArray()方法以字符串或者字节数组的形式返回。调用ByteArrayOuputStream的close()方法毫无软用,并不能真正关闭该输出流,方法调用之后其他操作该输出流的方法仍然可以被调用而不会抛出IOException异常。ByteArrayOutputStream额外提供了一个writeTo(OutputStream out)支持流之间的数据复制
二、ByteArrayOutputStream数据结构
1 - ByteArrayOutputStream继承结构
public class ByteArrayOutputStream extends OutputStream
ByteArrayInputStream继承自OutputStream,支持字节输出流的基本操作
2 - ByteArrayOutputStream的成员变量
/**
* 内部存储数据的缓冲区
*/
protected byte buf[];
/**
* 缓冲区存储的有效字节数
*/
protected int count;
/**
* 缓冲区数组允许分配的最大数组限制,超出该限制可能导致OutofMemoryError(内部溢出错误)
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
三、ByteArrayOutputStream源码分析
1 - 构造函数
/**
* 构造函数 内部缓冲区初始容量默认为32,需要的时候进行扩容
*/
public ByteArrayOutputStream() {
this(32);
}
/**
* 构造函数 指定参数size作为初始容量初始化内部缓冲区
*/
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
2 - void write(int b)方法
/**
* 将指定单个字节写入当前字节数组输出流,字节数据被保存在指定int值得低16为,高16位被自动丢弃
*/
public synchronized void write(int b) {
//确认写入字节后的缓冲区容量在需要的时候对内部缓冲区进行扩容
ensureCapacity(count + 1);
//在缓冲区数组指定位置放置指定的字节b
buf[count] = (byte) b;
//缓冲区有效字节计数器更新
count += 1;
}
write(int b)方法开始首先调用ensureCapacity确认写入单个字节后数组的容量,在需要的时候进行扩容,ensureCapacity是ByteArrayOutputStream非常核心的方法我们进入方法源码看下它的实现:
private void ensureCapacity(int minCapacity) {
if (minCapacity - buf.length > 0)
grow(minCapacity);
}
ensureCapacity方法内部对比了插入字节数据之后缓冲数组需要的最低容量和当前容量buf.length,若插入后需要的最低容量大于当前容量那么调用grow方法对缓冲区进行扩容,我们继续跟进grow方法源码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = buf.length;
int newCapacity = oldCapacity << 1;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
buf = Arrays.copyOf(buf, newCapacity);
}
grow方法源码揭示了ByteArrayOutputStream内部缓冲区一般情况下的扩容规则新的缓冲区(byte数组)在原缓冲区的基础上扩容2倍,其他情况包括若扩容后的数组长度低于ByteArrayOutputStream插入字节数据所需的最低容量,那么将后者指定为扩容之后的新缓冲区数组的长度,若经前面步骤确定的扩容后的容量大于最大容量限制Ingteger.MAX_VALUE-8,那么进行一次极限扩容,最大不超过最大限制MAX_VALUE(2^31-1),超过则取该值,基于前述步骤确定的扩容后容量创建同等大小的字节数组,将旧缓冲区中保存的字节数据填充到新数组中,最后内部缓冲区引用指向新数组到这一步就完成了ByteArrayOutputStream的内部缓冲区扩容。
我们回到write方法接着分析下面的代码,之后逻辑就很简单了,在缓冲区数组buf尾部位置count处插入新新写入的字节b,count++更新ByteArrayOutputStream维护的有效字节数计数器
3 - 其他成员方法
ByteArrayOutputStream的成员方法实现较为简单,我们就只选取核心方法write(int b)进行分析,其他方法我们这边只大概解释功能读者可以自行研读
/**
* 将指定单个字节写入当前字节数组输出流,字节数据被保存在指定int值得低16为,高16位被自动丢弃
*/
public synchronized void write(int b) {
//确认写入字节后的缓冲区容量在需要的时候对内部缓冲区进行扩容
ensureCapacity(count + 1);
//在缓冲区数组指定位置放置指定的字节b
buf[count] = (byte) b;
//缓冲区有效字节计数器更新
count += 1;
}
/**
* 将指定字节数组下标off开始之后的len个字节写入到当前字节数组输出流
*/
public synchronized void write(byte b[], int off, int len) {
//判断指定数组写入起始位置off和指定写入长度是否合法,即判断指定参数是否会发生数组越界
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) - b.length > 0)) {
throw new IndexOutOfBoundsException();
}
//确认写入字节数据后是否需要扩容
ensureCapacity(count + len);
//将指定字节数组buf从偏移量off开始的len个字节复制到缓冲区数组,count位置是数组复制起点
System.arraycopy(b, off, buf, count, len);
//内部有效字节数计数器更新
count += len;
}
/**
* 将当前输出流的所有内容复制到指定输出流out,主要是基于out.write(buf, 0,count)方法将内部缓冲数组
* buf写入指定输出流out
*/
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}
/**
* 将ByteArrayOutputStream对象的内部成员变量重置为0,这样,原本保存在内部缓冲区中的数据就会失效
* 内部缓冲数组空间可以被重复使用,也就是数组项的值可以被覆盖
*/
public synchronized void reset() {
count = 0;
}
/**
* 将当前输出流中保存的字节数据复制到一个新的字节数组中,数组长度为当前输出流的有效字节数,返回该数组
*/
public synchronized byte toByteArray()[] {
return Arrays.copyOf(buf, count);
}
/**
* 返回字节输出流中写入的字节数,即内部缓冲数组保存的有效字节数
*/
public synchronized int size() {
return count;
}
/**
* 使用平台默认的编码方式解码当前输出流缓冲区保存的字节数据.返回的字符串长度取决于平台采用的编码方式,
* 因此长度可能并不等于缓冲区存储的字节数,当遇到格式错误或者无法映射的字符时会基于平台的默认替换字符替换
* 它
*/
public synchronized String toString() {
return new String(buf, 0, count);
}
/**
* 将缓冲区中的字节数据转化为字符串,基于指定编码方式charsetName.新字符串的长度取决于特定的编码方式
* 当遇到格式错误或者无法映射的字符时会基于平台的默认替换字符替换它
* 它
*/
public synchronized String toString(String charsetName)
throws UnsupportedEncodingException
{
return new String(buf, 0, count, charsetName);
}
/**
* 不推荐使用
*/
@Deprecated
public synchronized String toString(int hibyte) {
return new String(buf, hibyte, 0, count);
}
/**
* 空实现
*/
public void close() throws IOException {
}