nputStream 是所有表示字节输入流类的父类。windows操作系统的JDK8版本中,所有的InputStream的子类如下(此处只展示部分):

clipboard

  常用的InputStream子类有以下5个:

clipboard

①、FileInputStream:处理文件的输入流。
②、ByteArrayInputStream :处理内存中byte[]数组的流式转换。
③、BufferedInputStream :带缓冲区的字节数组输入流,一般配合FileInputStream一起使用(缓冲区可以减少IO次数)。
④、ObjectInputStream:从流中读入一个自定义的对象。需要与ObjectOutputStream与配合使用,且按同样的顺序(写入的对象的顺序决定了读取对象的顺序)。
⑤、StringBufferInputStream:处理内存中String对象的流式转换。
以上5个子类的使用方式请参照:我的另一篇博客1、Java的IO概览(一)
  InputStream.class的源码如下:

package java.io;

public abstract class InputStream implements Closeable {

    // skip()函数中可以使用的最大缓冲区大小
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;
    //留给子类实现,子类必须遵守以下规则:
    //1、从输入的Stream中读取输入数据的下一个字节(ASCII码值),在读取到数据或者抛出异常前,这个函数是阻塞的。
    //2、返回一个0 ~ 255 的 ASCII码值 。如果因为已经读到了Stream末尾而没有可用的字节,则返回值 -1。
    public abstract int read() throws IOException;
     //将从输入的Stream中读取的ASCII码值放入到byte[]数组中,0表示从byte[]数组的第0个索引开始,b.length表示一次性向byte[]数组中放入的ASCII码值的数量
     //在读取到数据或者抛出异常前,这个函数是阻塞的。
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    //将从输入的Stream中读取的字节(ASCII码值)放入到byte[]数组中,off表示从byte[]数组的第off个索引开始,len表示一次性向byte[]数组中放入的字节(ASCII码值)的数量
     //在读取到数据或者抛出异常前,这个函数是阻塞的。
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {//byte[]数组不能为空
            throw new NullPointerException();
        //范围检测,off和len必须是非负数,b.length - off是byte[]数组还可以放的字节(ASCII码值)的数量
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();//范围检查失败,抛出一个IndexOutOfBoundsException异常
        } else if (len == 0) {//len=0,则直接返回0,该Stream可能有可读的字节(ASCII码值),也可能没有可读的字节(ASCII码值),但是本次不读任何数据。
            return 0;
        }

        int c = read();//最终还是调用子类实现的read()函数
        if (c == -1) {//read()函数规定了,返回值-1表示已经读到了Stream末尾而没有可用的字节(ASCII码值) 
            return -1;//如果一开始就读到了Stream末尾而没有可用的字节(ASCII码值),则直接返回-1 
        }
        b[off] = (byte)c;//如果一开始从Stream中可以读到字节(ASCII码值),则将读到的第1个字节(ASCII码值)值放入byte[]数组的第off个索引位置

        //如果从Stream中读到了第1个字节(ASCII码值),则接着从Stream中读后面的字节(ASCII码值)
        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();//最终还是调用子类实现的read()函数
                if (c == -1) {
                    break;//读不到,结束循环
                }
                b[off + i] = (byte)c;//每次从Stream中读到的字节(ASCII码值)都放到byte[]数组的第off个索引位置之后
            }
        } catch (IOException ee) {
        }
        return i;//返回从Stream中读到的字节数量
    }
    
    //将从输入的Stream中跳过n个字节
    public long skip(long n) throws IOException {
        //还没(或者还需要)跳过的字节(ASCII码值)的总数量
        long remaining = n;
        int nr;
        //校验,如果n<=0,则返回0
        if (n <= 0) {
            return 0;
        }
        //每次跳过的字节(ASCII码值)最多为2048个
        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        //用于每次跳过指定数量字节(每次最多为2048个)的数组
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {//还需要跳过的字节(ASCII码值)数量<=0时,跳出循环
            //调用read(byte b[], int off, int len)函数,该函数在上面已经分析过,该函数的返回值有3种,含义如下:
            //①、返回值=-1,表示该Stream没有可读的字节
            //②、返回值=0,表示该Stream可能有可读的字节(ASCII码值),也可能没有可读的字节(ASCII码值),但是本次传入的Math.min(size, remaining)为0,不从Stream中读任何数据,此处的Math.min(size, remaining)不可能为0
            //③、返回值>0,表示从该Stream中读到的字节(ASCII码值)的数量
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;//该Stream中没有可读的字节时,nr=-1,跳出循环
            }
            remaining -= nr;//表示还需要跳过的字节(ASCII码值)数量
        }

        return n - remaining;//返回已经跳过的字节(ASCII码值)的总数量
    }
    
    //返回这个Stream中还可以读取的字节的总数量,JDK不建议将这个函数的返回值作为缓冲区的长度来从Stream中读取数据(子类一般会覆盖这个函数)
    public int available() throws IOException {
        return 0;
    }
    
    //留给子类实现,子类必须遵守以下规则:
    //关闭Stream,并释放与该流相关的系统资源
    public void close() throws IOException {}
    
    //标记此Stream中的当前位置。随后调用reset()函数会将此流重新定位到上次标记的位置,从而使得后续的读取操作能够再次读取相同的字节。
    //带有回退功能的InputStream的子类会重写这个函数,但是FileInputStream不会重写这个函数,也就意味着,FileInputStream不支持回退功能
    public synchronized void mark(int readlimit) {}
    
    //reset()函数会将此流重新定位到上次标记的位置,从而使得后续的读取操作能够再次读取相同的字节。
    //带有回退功能的InputStream的子类会重写这个函数,但是FileInputStream不会重写这个函数,也就意味着,FileInputStream不支持回退功能
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }
    //如果InputStream的子类支持mark()函数和 reset()函数,则返回true,否则,返回false(InputStream的子类不支持mark()函数和 reset()函数)。
    public boolean markSupported() {
        return false;
    }

}
1.1、InputStream的skip()函数
    public long skip(long n) throws IOException {
        //还没(或者还需要)跳过的字节(ASCII码值)的总数量
        long remaining = n;
        int nr;
        //校验,如果n<=0,则返回0
        if (n <= 0) {
            return 0;
        }
        //每次跳过的字节(ASCII码值)最多为2048个
        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        //用于每次跳过指定数量字节(每次最多为2048个)的数组
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {//还需要跳过的字节(ASCII码值)数量<=0时,跳出循环
            //调用read(byte b[], int off, int len)函数,该函数在上面已经分析过,该函数的返回值有3种,含义如下:
            //①、返回值=-1,表示该Stream没有可读的字节
            //②、返回值=0,表示该Stream可能有可读的字节(ASCII码值),也可能没有可读的字节(ASCII码值),但是本次传入的Math.min(size, remaining)为0,不从Stream中读任何数据,此处的Math.min(size, remaining)不可能为0
            //③、返回值>0,表示从该Stream中读到的字节(ASCII码值)的数量
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;//该Stream中没有可读的字节时,nr=-1,跳出循环
            }
            remaining -= nr;//表示还需要跳过的字节(ASCII码值)数量
        }

        return n - remaining;//返回已经跳过的字节(ASCII码值)的总数量
    }

如果要从一个20000个字节的Stream中跳过6000个字节,只需要调用skip(6000)即可,该函数的执行过程分为以下4步:
①、while循环之前进行初始化byte[]数组和零时变量的操作

clipboard

②、第1次while循环之后,已经从前Stream中读取了2048个字节

clipboard

③、第2次while循环之后,已经从前Stream中读取了4096个字节

clipboard

④、第3次while循环之后,已经从前Stream中读取了6000个字节,读取完毕,byte[]数组的前1094个位置是本次从Stream流中读取的第4097第6000个字节,byte[]数组的后954个位置仍然是上一次从Stream流中读取的第3143第4096个字节

clipboard

二、FilterInputStream 源码——装饰器基类

  FilterInputStream 的UML关系图,如下所示:

clipboard

  FilterInputStream.class的源码,如下所示:

package java.io;
public
class FilterInputStream extends InputStream {
   //用来组合了一个 被装饰者的变量,被修饰为volatile 有以下3个原因:
    // 1. 确保多线程环境下修改的可见性
    // 2. 有些装饰器允许运行时替换底层流
    // 3. 防止指令重排序导致的初始化问题
    protected volatile InputStream in;
    //创建时传入一个 被装饰者
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    //调用被装饰者的read()函数
    public int read() throws IOException {
        return in.read();
    }
    //调用被装饰者的read(byte b[]) 函数
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    //调用被装饰者的read(byte b[], int off, int len)函数
    public int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }
    //调用被装饰者的skip()函数
    public long skip(long n) throws IOException {
        return in.skip(n);
    }
   //调用被装饰者的available()函数
    public int available() throws IOException {
        return in.available();
    }
   //调用被装饰者的close()函数
    public void close() throws IOException {
        in.close();
    }
    //调用被装饰者的mark()函数
    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }
    //调用被装饰者的reset()函数
    public synchronized void reset() throws IOException {
        in.reset();
    }
    //调用被装饰者的markSupported()函数
    public boolean markSupported() {
        return in.markSupported();
    }
}
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐