# 前言
在阅读本文之前,请先看完上一篇文章中 IO
的类设计使用了装饰者模式。 InputStream
表示输入,是指外界对程序进行数据的输入。它是一个抽象类,即表示所有字节输入流实现类的基类。它的作用就是抽象地表示所有从不同数据源产生输入的类。
# 核心方法
InputStream
的三个 read
是最核心的方法(JDK8),分别是
// 读取下一个字节的数据,如果没有则返回 - 1 | |
public abstract int read() | |
public int read(byte b[]) { | |
return read(b, 0, b.length); | |
} | |
// 具体实现之后分析 | |
public int read(byte b[], int off, int len) |
在 JDK9 和 JDK11 又新增了其他几个方法
// JDK9 新增:读取 InputStream 中的所有剩余字节,调用 readNBytes (Integer.MAX_VALUE) 方法 | |
public byte[] readAllBytes() | |
// JDK11 更新:读取 InputStream 中的剩余字节的指定上限大小的字节内容;此方法会一直阻塞,直到读取了请求的字节数、检测到流结束或引发异常为止。此方法不会关闭输入流。 | |
public byte[] readNBytes(int len) | |
// JDK9 新增:从输入流读取请求的字节数并保存在 byte 数组中; 此方法会一直阻塞,直到读取了请求的字节数、检测到流结束或引发异常为止。此方法不会关闭输入流。 | |
public int readNBytes(byte[] b, int off, int len) | |
// JDK9 新增:读取 InputStream 中的全部字节并写入到指定的 OutputStream 中 | |
public long transferTo(OutputStream out) |
其他的重要方法之后会在源码讲解提到。
# 源码解析
InputStream
有几个重要的属性
// 当使用 skip 方法时,最大的 buffer size 大小 | |
private static final int MAX_SKIP_BUFFER_SIZE = 2048; | |
// 默认的 buffer size(JDK8 没有该属性) | |
private static final int DEFAULT_BUFFER_SIZE = 8192; | |
// 由于一些 VM 在数组中保留一些头字,所以尝试分配较大的阵列可能会导致 | |
// OutOfMemoryError(请求的阵列大小超过 VM 限制) | |
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; |
read(byte b[], int off, int len)
方法:因为代码比较简单,读者可以自行在 IDE
中查看,这里给出流程图
readNBytes(int len)
方法:代码较长,但是都比较简单
// 读取 InputStream 中的剩余字节的指定上限大小的字节内容;此方法会一直阻塞, | |
// 直到读取了请求的字节数、检测到流结束或引发异常为止。此方法不会关闭输入流。 | |
public byte[] readNBytes(int len) throws IOException { | |
// 边界检查 | |
if (len < 0) { | |
throw new IllegalArgumentException("len < 0"); | |
} | |
List<byte[]> bufs = null; // 缓存每次读取到的内容放到 bufs,最后组装成 result | |
byte[] result = null; // 最后读取到的内容 | |
int total = 0; | |
int remaining = len; // 剩余字节长度 | |
int n; | |
do { | |
// 初始化缓存数组 | |
byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)]; | |
int nread = 0; | |
// 读取到结束为止,结束条件:要么 buf 被读满,要么 remaining==0 | |
while ((n = read(buf, nread, | |
Math.min(buf.length - nread, remaining))) > 0) { | |
nread += n; | |
remaining -= n; | |
} | |
if (nread > 0) { | |
if (MAX_BUFFER_SIZE - total < nread) { | |
throw new OutOfMemoryError("Required array size too large"); | |
} | |
total += nread; | |
if (result == null) { | |
result = buf; | |
} else { | |
if (bufs == null) { | |
bufs = new ArrayList<>(); | |
bufs.add(result); | |
} | |
bufs.add(buf); | |
} | |
} | |
// 如果读不到内容(返回 - 1)或者没有剩余的字节,则跳出循环 | |
} while (n >= 0 && remaining > 0); | |
if (bufs == null) { | |
if (result == null) { | |
return new byte[0]; | |
} | |
return result.length == total ? | |
result : Arrays.copyOf(result, total); | |
} | |
// 组装最后的 result | |
result = new byte[total]; | |
int offset = 0; | |
remaining = total; | |
for (byte[] b : bufs) { | |
int count = Math.min(b.length, remaining); | |
System.arraycopy(b, 0, result, offset, count); | |
offset += count; | |
remaining -= count; | |
} | |
return result; | |
} |
从这两个方法可以看出, InputStream
最核心的就是 read()
方法,子类如何实现该方法决定了子类的作用。所以类似其他的 int readNBytes(byte[],int,int)
之类的,其实现都很简单。
关于 read(byte[],int,int)
和 readNBytes(byte[],int,int)
的区别:
read(byte[], int, int)
是尝试读到最多len
个bytes
,但是读取到的内容长度可能是小于 len 的。readNBytes(byte[], int, int)
会一直(while
循环)查找直到stream
尾为止。
这里需要注意的是 skip
方法的实现
// 跳过指定个数的字节不读取 | |
public long skip(long n) throws IOException { | |
long remaining = n; | |
int nr; | |
if (n <= 0) { | |
return 0; | |
} | |
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); | |
byte[] skipBuffer = new byte[size]; | |
while (remaining > 0) { | |
nr = read(skipBuffer, 0, (int)Math.min(size, remaining)); | |
if (nr < 0) { | |
break; | |
} | |
remaining -= nr; | |
} | |
return n - remaining; | |
} |
skip
方法不是说在数据源中跳过 n
个字节然后再使用 read
读取,而是将这 n
个字节读到一个缓存数组中,方法结束后该数组又会被回收,但是文件指针在数据源中的位置确实移动了 n
个字节。
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); |
这行代码决定了跳过字节数是有限制的,每次循环最多是 2048
个字节。
transferTo
方法:
// JDK9 新增:读取 InputStream 中的全部字节并写入到指定的 OutputStream 中 | |
public long transferTo(OutputStream out) throws IOException { | |
Objects.requireNonNull(out, "out"); | |
long transferred = 0; | |
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; | |
int read; | |
while ((read = this.read(buffer, 0, DEFAULT_BUFFER_SIZE)) >= 0) { | |
out.write(buffer, 0, read); | |
transferred += read; | |
} | |
return transferred; | |
} |
# 参考
Java 全栈知识体系:https://pdai.tech/md/java/io/java-io-basic-code-inputstream.html
并发编程网:http://ifeve.com/java-io-%E4%B9%8B-inputstream%E6%BA%90%E7%A0%81/
stackoverflow:https://stackoverflow.com/questions/53754387/java-read-vs-readnbytes-of-the-inputstream-instance