# 前言

在阅读本文之前,请先看完上一篇文章中 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 中查看,这里给出流程图

read方法

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) 是尝试读到最多 lenbytes ,但是读取到的内容长度可能是小于 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