这是《水煮 JDK 源码》系列 的第3篇文章,计划撰写100篇关于JDK源码相关的文章
ByteArrayInputStream
类位于 java.io
包下,继承于 InputStream
类,表示字节数组输入流,它会在内存中创建一个字节数组缓冲区,然后把从输入流中读取的数据全部保存在缓冲区中,其 UML 类图如下:
::: hljs-center
:::
1、成员变量
在 ByteArrayInputStream
中定义了4个成员变量,如下:
/** 存放数据的字节数组缓冲区 */
protected byte buf[];
/** 从字符数组缓冲区读取的下一个字符的位置索引 */
protected int pos;
/** 当前流中标记的位置,初始化时为0 */
protected int mark = 0;
/** 字节数组缓冲区中有效的字节数 */
protected int count;
2、构造方法
创建 ByteArrayInputStream
字节数组输入流主要有以下的两种方式:
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
// 第一个要读取的字符位置索引为0
this.pos = 0;
this.count = buf.length;
}
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
// 第一个读取的位置索引为 offset
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
// 标记位置也为 offset
this.mark = offset;
}
3、读取字节方法
ByteArrayInputStream
提供了两个读取字节的方法,如下:
public synchronized int read() {
// 如果下一个读取的字符位置大于字节数组的长度,直接返回 -1,说明此时数组已经读完了
// 每读取一个字节后,pos的位置就自动 +1
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
/** 将字节数组输入流中从 off 位置开始之后的最多 len 个长度的字节数据,读取到参数数组 b[] 中,返回的是实际读取的数据长度 */
public synchronized int read(byte b[], int off, int len) {
// 如果参数数组为null,则直接抛出空指针异常
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
// 数组越界检查
throw new IndexOutOfBoundsException();
}
// 如果输入流中已经没有可读数据了,直接返回-1
if (pos >= count) {
return -1;
}
// 输入流中可读取的数据长度 avail
int avail = count - pos;
// 如果希望从流中读取的数据长度 len 大于流中可读取的长度 avail,
// 那么将最多从流中读取 avail 个字节数据
if (len > avail) {
len = avail;
}
if (len <= 0) {
return 0;
}
// 使用 System.arraycopy 方法从 buf[] 中复制字节数据到参数 b[] 中
System.arraycopy(buf, pos, b, off, len);
// 读取完后,改变 pos 的位置索引
pos += len;
return len;
}
上面的两个 read()
方法都使用了 synchronized
关键字,即表示为同步方法,从具体的实现上可以看出,ByteArrayInputStream
中的数据是不可以重复读取的,每读取一个字节数据,下一个可读取的字节位置索引便为自动+1,如果已经读取到了字节数组的最后一个元素,则不能再读取了。
4、其他方法
ByteArrayInputStream
类提供了其他的一些方法,如下:
public synchronized long skip(long n)
:从输入流中跳过 n 个字节;public synchronized int available()
:获取还可以从输入流中读取的字节长度;public boolean markSupported()
:判断输入流是否支持标记功能,默认返回 true,即ByteArrayInputStream
是支持的;public void mark(int readAheadLimit)
:设置输入流中的标记位置为当前可读取的位置;public synchronized void reset()
:重置输入流中下一个可读取位置为标记位置;public void close()
:关闭输入流;
下面分别看看这些方法的具体实现。
public synchronized long skip(long n) {
// 获取当前输入流中可读取的数据长度 k
long k = count - pos;
// 判断需要跳过的长度 n 是否小于可读取的长度 k
if (n < k) {
k = n < 0 ? 0 : n;
}
// 调整下一个可读取的字节位置索引,直接偏移 k 个长度
pos += k;
// 返回的是实际跳过的字节数据长度,不一定等于 n
return k;
}
public synchronized int available() {
// 获取还可以从输入流中读取的字节长度
return count - pos;
}
public boolean markSupported() {
// 判断输入流是否支持标记功能,默认返回 true
return true;
}
public void mark(int readAheadLimit) {
// 设置输入流中的标记位置为当前可读取的位置,其中参数 readAheadLimit 没有任何用途
mark = pos;
}
public synchronized void reset() {
// 重置输入流中下一个可读取的位置为标记位置
pos = mark;
}
public void close() throws IOException {
// 没有任何代码实现
}
和 ByteArrayOutputStream
一样,ByteArrayInputStream
类中的 close()
方法也是没有任何代码实现的,即使被调用,也没有任何的作用。
5、示例代码
5.1 简单数据读取
上面分析了 ByteArrayInputStream
类的相关源码,下面通过一个简单的示例来看看 ByteArrayInputStream
该如何使用。
package com.magic.test;
import java.io.ByteArrayInputStream;
public class ByteArrayInputStreamTest {
public static void main(String[] args) {
byte[] bytes = "01234".getBytes();
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
int c;
while ((c = inputStream.read()) != -1) {
System.out.println(Character.toUpperCase((char) c));
}
}
}
运行后,输出的结果如下:
0
1
2
3
4
5.2 验证不可重复读
下面来验证一下输入流是不可以重复读的。
package com.magic.test;
import java.io.ByteArrayInputStream;
public class ByteArrayInputStreamTest {
public static void main(String[] args) {
byte[] bytes = "01234".getBytes();
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
int c;
System.out.println("第1次读取");
while ((c = inputStream.read()) != -1) {
System.out.println(Character.toUpperCase((char) c));
}
System.out.println("第2次读取");
while ((c = inputStream.read()) != -1) {
System.out.println(Character.toUpperCase((char) c));
}
}
}
运行后,输出结果如下:
第1次读取
0
1
2
3
4
第2次读取
5.3 可重复读取实现
从上面的结果可以看出,第2次读取时,未读取到任何数据,如果希望能够再次读取,那么又该如何处理呢?可以在第1次读取完后,使用 reset()
方法进行重置。
package com.magic.test;
import java.io.ByteArrayInputStream;
public class ByteArrayInputStreamTest {
public static void main(String[] args) {
byte[] bytes = "01234".getBytes();
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
int c;
System.out.println("第1次读取");
while ((c = inputStream.read()) != -1) {
System.out.println(Character.toUpperCase((char) c));
}
// 使用 reset 方法进行重置,从上面对 reset() 方法源码可以看出
// 重置后 pos = mark,而 mark 的默认值为 0,此时 pos = 0
// 那么又可以从索引位置 0 开始读取字节数据了
inputStream.reset();
System.out.println("第2次读取");
while ((c = inputStream.read()) != -1) {
System.out.println(Character.toUpperCase((char) c));
}
}
}
运行后输出结果如下:
第1次读取
0
1
2
3
4
第2次读取
0
1
2
3
4