ByteArrayOutputStream 类源码分析

网友投稿 648 2022-10-26

ByteArrayOutputStream 类源码分析

ByteArrayOutputStream 类源码分析

这是《水煮 JDK 源码》系列 的第2篇文章,计划撰写100篇关于JDK源码相关的文章

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

::: hljs-center

:::

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() 方法没有任何实现,即使调用该方法,也没有任何作用。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:数据结构---ArrayList源码分析
下一篇:kpages- 以api 为中心的高性能web 框架
相关文章

 发表评论

暂时没有评论,来抢沙发吧~