从JDK源码看InputStream

概况

JDK 给咱们提供了不少实用的输入流 xxxInputStream,而 InputStream 是全部字节输入流的抽象。包括 ByteArrayInputStream 、FilterInputStream 、BufferedInputStream 、DataInputStream 和 PushbackInputStream 等等。html

继承结构

--java.lang.Object
  --java.io.InputStream复制代码

类定义

public abstract class InputStream implements Closeable复制代码

InputStream 被定为 public 且 abstract 的类,实现了Closeable接口。java

Closeable 接口表示 InputStream 能够被close,接口定义以下:数组

public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}复制代码

主要属性

private static final int MAX_SKIP_BUFFER_SIZE = 2048;

private static final int DEFAULT_BUFFER_SIZE = 8192;

private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;复制代码
  • MAX_SKIP_BUFFER_SIZE 表示输入流每次最多能跳过的字节数。
  • DEFAULT_BUFFER_SIZE 默认的缓冲大小。
  • MAX_BUFFER_SIZE 表示最大的缓冲数组大小,这里设置为 Integer.MAX_VALUE - 8 这里也是考虑到 JVM 能支持的大小,超过这个值就会致使 OutOfMemoryError。

主要方法

read方法

一共有三个 read 方法,其中有一个抽象的 read 方法,其他两个 read 方法都会调用这个抽象方法,该方法用于从输入流读取下一个字节,返回一个0到255范围的值。若是已经到达输入流结尾处而致使无可读字节则返回-1,同时,此方法为阻塞方法,解除阻塞的条件:bash

  1. 有可读的字节。
  2. 检测到已是输入流的结尾了。
  3. 抛出异常。

主要看第三个 read 方法便可,它传入的三个参数,byte数组、偏移量和数组长度。该方法主要是从输入流中读取指定长度的字节数据到字节数组中,须要注意的是这里只是尝试去读取长度为 len 的数组,但真正读取到的数组长度不必定为 len,返回值才是真正读取到的长度。并发

public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }复制代码

看看它的逻辑,数组为null则抛空指针,偏移量和长度超过边界也抛异常,长度为0则什么都不敢直接返回0。接着调用 read() 读取一个字节,若是为-1则说明结束,直接返回-1。不然继续根据数组长度循环调用 read() 方法读取字节,而且填充到传入的数组对象中,最后返回读取的字节数。优化

readAllBytes方法

该方法从输入流读取全部剩余的字节,在此过程是阻塞的,直到全部剩余字节都被读取或到达流的结尾或发生异常。ui

逻辑是用一个 for 循环内嵌一个 while 循环,while 循环不断调用 read 方法尝试将 DEFAULT_BUFFER_SIZE 长度的字节数组填满,一旦填满则须要将数组容量扩容一倍,再将原字节数组复制到新数组中,而后再经过 while 循环继续读取,直到达到尾部才跳出 for 循环,最后返回读取到的全部字节数组。this

public byte[] readAllBytes() throws IOException {
        byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
        int capacity = buf.length;
        int nread = 0;
        int n;
        for (;;) {
            while ((n = read(buf, nread, capacity - nread)) > 0)
                nread += n;
            if (n < 0)
                break;
            if (capacity <= MAX_BUFFER_SIZE - capacity) {
                capacity = capacity << 1;
            } else {
                if (capacity == MAX_BUFFER_SIZE)
                    throw new OutOfMemoryError("Required array size too large");
                capacity = MAX_BUFFER_SIZE;
            }
            buf = Arrays.copyOf(buf, capacity);
        }
        return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
    }复制代码

readNBytes方法

从输入流中读取指定长度的字节,并且它能保证必定能读取到指定的长度,它属于阻塞方式,用一个 while 循环不断调用 read 读取字节,直到读取到指定长度才结束读取。spa

public int readNBytes(byte[] b, int off, int len) throws IOException {
        Objects.requireNonNull(b);
        if (off < 0 || len < 0 || len > b.length - off)
            throw new IndexOutOfBoundsException();
        int n = 0;
        while (n < len) {
            int count = read(b, off + n, len - n);
            if (count < 0)
                break;
            n += count;
        }
        return n;
    }复制代码

available方法

返回从该输入流能进行非阻塞读取的剩余字节数,当调用 read 读取的字节数通常会小于该值,有一些InputStream的子实现类会经过该方法返回流的剩余总字节数,但有些并不会,因此使用时要注意点。.net

这里抽象类直接返回0,子类中重写该方法。

public int available() throws IOException {
        return 0;
    }复制代码

skip方法

从输入流中跳过指定个数字节,返回值为真正跳过的个数。这里的实现是简单经过不断调用 read 方法来实现跳过逻辑,但这是较低效的,子类可用更高效的方式重写此方法。

下面看看逻辑,最大的跳过长度不能超过 MAX_SKIP_BUFFER_SIZE ,而且用一个 while 循环调用 read 方法,若是遇到返回为-1,即已经到达结尾了,则跳出循环。能够看到 skipBuffer 实际上是没有什么做用,直接让其被 GC 便可,最后返回真正跳过的字节数。

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;
    }复制代码

close方法

此方法用于关闭输入流,而且释放相关资源 。

public void close() throws IOException {}复制代码

transferTo方法

从输入流中按顺序读取所有字节而且写入到指定的输出流中,返回值为转移的字节数。转移过程当中可能会发生不肯定次的阻塞,阻塞可能发生在 read 操做或 write 操做。

主要逻辑是用 while 循环不断调用 read 方法操做读取字节,而后调用输出流的 write 方法写入,直到读取返回-1,即达到结尾。最后返回转移的字节数。

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;
    }复制代码

markSupported方法

是否支持 mark 和 reset 操做,这里直接返回 false,子类根据实际重写该方法。

public boolean markSupported() {
        return false;
    }复制代码

mark方法

标记输入流当前位置,与之对应的是 reset 方法,经过他们之间的组合能实现重复读取操做。另外它会传入 readlimit 参数,它用于表示该输入流中在执行 mark 操做后最多能够读 readlimit 个字节后才使 mark 的位置失效。

能够看到 InputStream 的 mark 方法是什么都不作的,子类中再具体实现。

public synchronized void mark(int readlimit) {}复制代码

reset方法

与 mark 方法对应,它能够重置输入流的位置到上次被 mark 操做标识的位置。InputStream 的 reset 方法直接抛出一个 IOException,子类中根据实际状况实现。

public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }复制代码

如下是广告相关阅读

========广告时间========

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有须要的朋友能够到 item.jd.com/12185360.ht… 进行预约。感谢各位朋友。

为何写《Tomcat内核设计剖析》

=========================

相关阅读:

从JDK源码角度看Object
从JDK源码角度看Long
从JDK源码角度看Integer
volatile足以保证数据同步吗
谈谈Java基础数据类型
从JDK源码角度看并发锁的优化
从JDK源码角度看线程的阻塞和唤醒
从JDK源码角度看并发竞争的超时
从JDK源码角度看java并发线程的中断
从JDK源码角度看Java并发的公平性
从JDK源码角度看java并发的原子性如何保证
从JDK源码看Writer
从JDK源码看关闭钩子
从JDK源码角度看Byte
从JDK源码角度看Boolean
从JDK源码角度看Short

欢迎关注:

这里写图片描述
这里写图片描述

这里写图片描述
这里写图片描述
相关文章
相关标签/搜索