ByteArrayOutputStream 类位于 java.io 包下,继承于 OutputStream 类,从字面上可以看出,它表示的是一个字节数组输出流。它的实现方式是先在内存中创建一个字节数组缓冲区 byte buf[],然后把所有发送到输出流的数据保存于字节数组缓冲区中,其中字节数组缓冲区会随着数据的增加而自动调整大小,其UML 类图如下:

1、构造函数
ByteArrayOutputStream 类提供两个构造方法,分别如下:
public ByteArrayOutputStream() {
this(32);
}
public ByteArrayOutputStream(int size) {
// 传入的 size 不能小于 0
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}无参构造方法默认创建一个32字节的缓冲区,而另一个构造方法则是创建指定大小为 size 的缓冲区。
ByteArrayOutputStream 类中有3个成员变量 buf[] 、 count 和 MAX_ARRAY_SIZE,其定义如下:
/**
* 字节数组.
*/
protected byte buf[];
/**
* 字节数组大小.
*/
protected int count;
/** 最大数组大小 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;在成功创建字节数组输出流后,就可以调用相应的方法进行操作,操作方法主要分为下面几类:
- 写入字节数据方法
- 字节数组扩容方法
- 将字节数组转换为字符串方法
- 其他方法
2、写入字节数据方法
ByteArrayOutputStream 提供了2个写入方法,1个写入到其他输出流方法,分别如下:
-
public synchronized void write(int b):将指定的字节写入字节数组输出流; -
public synchronized void write(byte b[], int off, int len):将指定字节数组中从偏移量 off 开始的 len长度的字节写入字节数组输出流中 -
public synchronized void writeTo(OutputStream out) throws IOException:将字节数组输出流中的全部数据写入到输出流参数中,调用的是OutputStream的write()方法
这3个写入方法都使用 synchronized 关键字,即为同步方法。
public synchronized void write(int b) {
// 首先检查字节数组大小,由于写入了 b,所以新的数组容量至少为 count + 1
// count 代表之前写入的数据大小
ensureCapacity(count + 1);
// 写入的新数据存放在数组的最后
buf[count] = (byte) b;
count += 1;
}
public synchronized void write(byte b[], int off, int len) {
// 首先检查要写入的数据是否越界了
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) - b.length > 0)) {
throw new IndexOutOfBoundsException();
}
// 检查字节数组大小,由于要新写入len长度的字节数据,所以最小容量为 count + len
ensureCapacity(count + len);
// 复制数组 b[] 中的数据到 buf[] 中
System.arraycopy(b, off, buf, count, len);
count += len;
}
public synchronized void writeTo(OutputStream out) throws IOException {
// 将字节数组中所有的数据全部写入到输出流参数中
out.write(buf, 0, count);
}在向字节数组中写入新数据时,首先要做的就是检查当前数组的容量,如果容量不足,则需要先对数组进行扩容,然后再保存数据;如果同时写入多个字节数据,将会使用 System.arraycopy() 方法进行数组拷贝,它是一个 native 方法,其定义如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);3、字节数组扩容方法
ByteArrayOutputStream 可以实现自动对字节数组进行扩容,当数组大小无法存放新的字节内容时,就会自动进行扩容,数组的扩容主要涉及到下面的三个方法:
-
private void ensureCapacity(int minCapacity):检查字节数组容量大小 -
private void grow(int minCapacity):字节数组扩容 -
private static int hugeCapacity(int minCapacity):检查是否超过最大容量,最大容量为Integer.MAX_VALUE - 8
在 write() 方法中可以看到,当向字节数组中写入数据时,会先调用 ensureCapacity() 方法检查数组的容量,如果容量不足,则会进行扩容,其实现如下:
private void ensureCapacity(int minCapacity) {
// overflow-conscious code
// 如果数组容量不足,则进行扩容操作
if (minCapacity - buf.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
// 原字节数组的长度
int oldCapacity = buf.length;
// 将原数组长度左移1位(相当于乘以2),得到新字节数组的长度
// 扩容后的数组长度为扩容前的2倍
int newCapacity = oldCapacity << 1;
// 如果扩容后的数组长度小于最小所需长度的话,则新的长度等于最小所需长度
// 比如原数组长度为32,保存了32个字节数据,现在如果新增1个字节,那么此时
// 最小所需长度为33,而 newCapacity 此时为64
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 判断默认扩容后的数组大小是否超过了数组容量最大值
// 如果默认扩容后的大小超过了最大值,则直接判断最小所需大小是否超过最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 对原数组进行复制扩容
buf = Arrays.copyOf(buf, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 如果所需最小容量小于0,直接抛出OOM错误
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 比较所需最小容量与最大数组大小,如果所需最小容量更大,则扩容后的数组长度为 Integer.MAX_VALUE
// 否则扩容后长度为最大数组长度,即为 Integer.MAX_VALUE - 8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}在对数据进行扩容操作时,默认的扩容策略是直接将数组大小增加1倍,如果扩容后仍然不够,则等于所需最小容量大小,然后在与数组最大值进行比较,判断扩容后的数组是否超过允许的最大值。
数据扩容时,调用的是 Arrays.copyOf() ,该方法是位于 java.util 包下 Arrays 类的方法, 其定义如下:
public static byte[] copyOf(byte[] original, int newLength) {
// 创建一个长度为 newLength 的新字节数组
byte[] copy = new byte[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}Arrays.copyOf()方法的实现方式是先创建一个长度为 newLength 的新字节数组,然后使用 System.arraycopy() 方法进行数组数据的拷贝,最后返回新的数组,也就是说 ByteArrayOutputStream 进行数组扩容时,都会执行数据拷贝的动作。
4、将字节数组转换为字符串方法
ByteArrayOutputStream 提供了3个将字节数组转换为字符串的方法,其中有1个已经被废弃,具体如下:
-
public synchronized String toString():根据默认字符编码将缓冲区的字节内容转换为字符串 -
public synchronized String toString(String charsetName):根据指定字符编码将缓冲区的字节内容转换为字符串 -
public synchronized String toString(int hibyte):该方法已废弃
3个 toString() 方法都是使用了关键词 synchronized,即为同步方法。
public synchronized String toString() {
return new String(buf, 0, count);
}
public synchronized String toString(String charsetName)
throws UnsupportedEncodingException
{
// 根据指定字符编码将缓冲区的字节内容转换为字符串
// charsetName 不能为 null
return new String(buf, 0, count, charsetName);
}
@Deprecated
public synchronized String toString(int hibyte) {
// 已废弃
return new String(buf, hibyte, 0, count);
}将字节数组转换为字符串的实现都比较简单,都是直接 new String() 创建一个新的字符串,在转换的时候可以使用系统默认的字符编码,也可以使用指定的字符编码,关于上面两个 new String() 方法实现如下:
public String(byte bytes[], int offset, int length) {
// 检查数组边界,避免数组越界取值
checkBounds(bytes, offset, length);
// 调用 StringCoding.decode 方法将字节数组转换为字符串,使用默认字符编码
// 默认字符编码获取方式为 Charset.defaultCharset().name();
this.value = StringCoding.decode(bytes, offset, length);
}
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
// charsetName 不能为 null,否则会抛出空指针异常
if (charsetName == null)
throw new NullPointerException("charsetName");
// 检查数组边界,避免数组越界取值
checkBounds(bytes, offset, length);
// 调用 StringCoding.decode 方法将字节数组转换为字符串
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}关于系统默认的字符编码格式获取方法为:Charset.defaultCharset().name(),其具体实现如下:
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = AccessController.doPrivileged(
new GetPropertyAction("file.encoding"));
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}
return defaultCharset;
}从上面的实现可以看出,如果默认编码不存在,则使用 UTF-8 编码格式。
5、其他方法
ByteArrayOutputStream 提供的其他方法主要有以下这些:
-
public synchronized void reset():将字节数组输出流的count字段重新置为0,丢弃所有已累积的数据输出; -
public synchronized byte toByteArray()[]:拷贝并创建一个新的字节数组,数组大小和内容与当前输出流一致; -
public synchronized int size():获取字节数组的大小; -
public void close() throws IOException:关闭字节数组输出流
下面分别看看这些方法的具体实现。
public synchronized void reset() {
// reset 方法是直接将 count 置为0,count代表的是字节数组中有效的字节数
count = 0;
}
public synchronized byte toByteArray()[] {
// 使用 Arrays.copyof() 方法拷贝并创建一个新的字节数组
// Arrays.copyof() 方法在上面已经分析过,具体是使用 System.arraycopy() 实现
return Arrays.copyOf(buf, count);
}
public synchronized int size() {
// 返回字节数组中有效字节的大小,即数组中保存了多少字节数据
return count;
}
public void close() throws IOException {
// 该方法没有任何实现,即调用 close() 方法,没有任何本质作用
}需要注意的是 ByteArrayOutputStream 的 close() 方法没有任何实现,即使调用该方法,也没有任何作用。
















