Java I/O 11 - ByteArrayInputStream & ByteArrayOutputStream

  关于 java.io.ByteArrayInputStream java.io.ByteArrayOutputStream 的部分笔记,这两个类相互合作借助内存来完成数据的读写和转移。本文演示代码段的执行环境基于JDK版本1.7

概述

  ByteArrayInputStream和ByteArrayOutputStream没有太多涉及到和其他输入输出流进行交互的设计和实现,更多的是通过这两个类内部维护在内存中的字节数组来完成数据的读写和转移。对于一些不需要做持久处理的数据而言,以内存作为中转站来完成数据的操作和转移相对于写文件或者数据库等持久化处理来说性能更乐观,同时也省掉了很多不必要的、没有实际价值的处理和操作。

继承关系

1
2
3
4
5
6
7
8
9
// ByteArrayInputStream
--java.lang.Object
--java.io.InputStream
--java.io.ByteArrayInputStream

// ByteArrayOutputStream
--java.lang.Object
--java.io.InputStream
--java.io.ByteArrayOutputStream

实现接口

类名 实现接口
ByteArrayInputStream Closeable, AutoCloseable
ByteArrayOutputStream Closeable, Flushable, AutoCloseable

ByteArrayInputStream

Constructor Summary

public ByteArrayInputStream(byte buf[])

1
2
3
4
5
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}

  参数是一个byte类型的数组,该数组的内容会被指向输入流中的字段buf,所以入参buf的内容会成为输入流方法read()中的数据源。pos指向了下一个可以读取数据的位置。count指向了缓冲区数组中数据结束的位置。

public ByteArrayInputStream(byte buf[], int offset, int length)

1
2
3
4
5
6
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}

  参数是一个byte类型的数组,该数组的内容会被指向输入流中的字段buf,所以入参buf的内容会成为输入流方法read()中的数据源。由参数offset确定pos指向的下一个可以读取数据的位置,同时指定mark,因为offset可能不为0,所以通过mark可以保证offset之前的数据永远不会被读到。

部分方法

public synchronized int read()

1
2
3
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}

  返回缓冲区数组中的当前读取位置的数据。由于数组中存储的是byte类型,所以需要通过“0xff”来完成向int类型的转换。如果当前pos位置大于等于count,那么认为数据已经读完了,所以返回-1

public ynchronized int read(byte b[], int off, int len)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public synchronized int read(byte b[], int off, int len) {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}

if (pos >= count) {
return -1;
}

int avail = count - pos;
if (len > avail) {
len = avail;
}
if (len <= 0) {
return 0;
}
System.arraycopy(buf, pos, b, off, len);
pos += len;
return len;
}

  将数据读取到入参数组b中。第2 ~ 6行代码用来完成相关参数的校验,保证不会发生越界问题。第8 ~ 10行代码如果当前读取位置大于等于count,那么认为缓冲区数组中已经没有可读数据了,所以返回-1标识文件读取结束。第12行计算缓冲区数组中剩余的未读取数据的容量,如果入参长度len大于第12行代码的计算结果,那么就把缓冲区数组中的内容全部返回给数组b。第19行代码用来完成数据复制和填充,之后会更新pos的值,最后返回实际读取的数据长度给方法调用方。

public synchronized long skip(long n)

1
2
3
4
5
6
7
8
9
public synchronized long skip(long n) {
long k = count - pos;
if (n < k) {
k = n < 0 ? 0 : n;
}

pos += k;
return k;
}

  跳过输入流缓冲区数组中指定长度的内容。第2行代码首先计算了缓冲区数组中剩余的未读取的数据长度。第3 ~ 5行则计算缓冲区数组的剩余未读取数据的长度和入参n的大小关系,如果缓冲区数组中的剩余数据长度大于n,那么实际跳过长度等于入参n,否则实际跳过长度等于缓冲区数组的剩余数据长度(即直接跳到缓冲区数组尾部)。如果入参n小于0,那么跳过长度为0。第7行代码计算了跳过后的pos值,并返回实际跳过的长度给方法调用方。

public synchronized int available()

1
2
3
public synchronized int available() {
return count - pos;
}

  计算缓冲区数组中剩余的未读取的数据长度。

public boolean markSupported()

1
2
3
public boolean markSupported() {
return true;
}

  ByteArrayInputStream支持mark标记。

public void mark(int readAheadLimit)

1
2
3
public void mark(int readAheadLimit) {
mark = pos;
}

  重复读取打标。方法入参readAheadLimit没有任何意义。在调用过若干次read()方法后调用mark()方法,可以设置一个mark标记位置,该位置记录希望重复读取数据的起点。如果下面的reset()方法恢复pos位置到上一次调用mark()方法的位置。

public synchronized void reset()

1
2
3
public synchronized void reset() {
pos = mark;
}

  设置重读取位置。

public void close()

1
2
public void close() throws IOException {
}

  ByteArrayInputStream的关闭方法不执行任何操作,涉及的资源会直接被GC回收和释放掉。

ByteArrayOutputStream

Constructor Summary

public ByteArrayOutputStream()

1
2
3
public ByteArrayOutputStream() {
this(32);
}

  初始化一个输出流。设置缓冲区数组的大小为32个长度。

public ByteArrayOutputStream(int size)

1
2
3
4
5
6
public ByteArrayOutputStream(int size) {
if (size < 0) {
throw new IllegalArgumentException("Negative initial size: " + size);
}
buf = new byte[size];
}

  初始化一个输出流。设置缓冲区数组的大小为入参size个长度。

部分方法

public synchronized void write(int b)

1
2
3
4
5
public synchronized void write(int b) {
ensureCapacity(count + 1);
buf[count] = (byte) b;
count += 1;
}

  向缓冲区数组中写入一个字节的数据。第2行代码首先会判断当前缓冲区数组中是否有足够的空间容纳新写入的内容,如果空间不足,那么会对当前缓冲区数组做扩容处理。然后将数据写入到缓冲区数组中,同时将count值加一。

public synchronized void write(byte b[], int off, int len)

1
2
3
4
5
6
7
8
9
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();
}
ensureCapacity(count + len);
System.arraycopy(b, off, buf, count, len);
count += len;
}

  向缓冲区数组中写入入参数组b中的内容。第2 ~ 5行代码用来完成入参的有效性校验,保证不会发生溢出越界的情况。第6行代码判断当前缓冲区数组中是否有足够的空间容纳新写入的内容,如果空间不足,那么会对当前缓冲区数组做扩容处理。第7行代码将数组b中的内容维护到缓冲区数组中,同时维护count值。

private void ensureCapacity(int minCapacity)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
int newCapacity = oldCapacity << 1;
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) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

  用来完成缓冲区数组剩余空间不足以容纳新写入数据时的扩容处理。第3行代码计算缓冲区数组是否有足够的剩余空间容纳新数据。如果空间不足,那么调用grow()方法完成扩充处理。第9 ~ 10行代码首先将现有缓冲区数组的长度扩大一倍,如果扩大后的长度还是比实际需要的长度小,那么就用实际需要的长度作为缓冲区数组的长度。第13 ~ 14行代码中,如果newCapacity超出了ByteArrayOutputStream种规定的可分配的最大数组长度(Integer.MAX_VALUE - 8)那么就根据实际所需长度和MAX_ARRAY_SIZE的关系来决定最终分配的数据长度。最后完成缓冲区数组的扩容,同时将旧缓冲区数组中的内容维护到新数组中去。

public synchronized void writeTo(OutputStream out)

1
2
3
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}

  向指定输出流中把缓冲区数组的内容写进去。

public synchronized void reset()

1
2
3
public synchronized void reset() {
count = 0;
}

  清空缓冲区数组。其中包含的数据都会被清空。

public synchronized byte toByteArray()[]

1
2
3
public synchronized byte toByteArray()[] {
return Arrays.copyOf(buf, count);
}

  将当前缓冲区数组转成新的byte数组。会把当前缓冲区数组中的内容完整的复制一份到新的byte数组中去。

public synchronized int size()

1
2
3
public synchronized int size() {
return count;
}

  返回当前缓冲区数组中有效内容的长度。

public synchronized String toString(String charsetName)

1
2
3
4
5
public synchronized String toString(String charsetName)
throws UnsupportedEncodingException
{
return new String(buf, 0, count, charsetName);
}

  把缓冲区数组中的内容转成一个String字符串并返回。转换使用的字符集采用的是方法传入的字符集。转化后的字符串长度因为字符集编码的不同可能不会等同于缓冲区数组的长度。

public synchronized String toString()

1
2
3
public synchronized String toString() {
return new String(buf, 0, count);
}

  把缓冲区数组中的内容转成一个String字符串并返回。转换使用的字符集采用的是平台默认的字符集。转化后的字符串长度因为字符集编码的不同可能不会等同于缓冲区数组的长度。

public void close()

1
2
public void close()  throws IOException {
}

  ByteArrayInputStream的关闭方法不执行任何操作,涉及的资源会直接被GC回收和释放掉。

涉及基础知识点

  1. NIL

参考文献

  1. Windsor90. 【Java基础知识】IO流—内存操作流ByteArrayInputStream、ByteArrayOutputStream [E]
  2. yuleichun. 在java开发过程中什么时候使用ByteArrayInputStream和ByteArrayOuitputStream? [E]




------------- End of this article, thanks! -------------


  版权声明:本文由Nathan R. Lee创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处。
  本文作者为 Nathan R. Lee
  本文标题为 Java I/O 11 - ByteArrayInputStream & ByteArrayOutputStream
  本文链接为 https://marcuseddie.github.io/2018/java-ByteArrayInputStream-ByteArrayOutputStream.html