java.io.BufferedInputStream 源码分析

BufferedInputStream 是带有缓存区的输入字节流,使用byte[ ] 数组来进行缓存。当需要从外界读入数据到内存中时,先一次性读取多个字节的数据存放到byte[ ] 数组当中。之后对数据的存取都是操作byte[ ] 数组这个缓存区,从而提高了性能。

 BufferedInputStream类结构如下所示:

在java的io模块中经常看到装饰器模式的身影,FilterInputStream 利用装饰器模式将InputStream 封装成类的成员变量,可以看到FilterInputStream 类其实并没有做什么特殊处理而是将所有操作全都委托 in 对象处理。

class FilterInputStream extends InputStream {

    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }
    ... ...
}

BufferedInputStream 继承 FilterInputStream ,其成员变量如下所示:

public class BufferedInputStream extends FilterInputStream {

    //默认的缓存区大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    //最大缓冲区大小
    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
    //缓存区
    protected volatile byte buf[];
    //缓存区可读字节总数
    protected int count;
    //缓存区当前可读位置
    protected int pos;
    //标记位
    protected int markpos = -1;
    //在读取marklimit个字节后,markpos标记会失效
    protected int marklimit;

    。。。 。。。
}

BufferedInputStream 类的mark(int readlimit) 是在缓存区的当前pos位置设置一个markpos标记,后续读取readlimit(marklimit)个字节之前,markpos 标记依然有效。markpos 标记有效时,可以调用reset 方法将pos重置到markpos位置,这样就可以回头重复读取之前读取过的数据。

BufferedInputStream 类的read(byte b[], int off, int len) ,从byte[] b的off索引位置开始将长度为len个字节的数据读入到byte[] b 中:

其核心操作是:1、前置校验 2、如果缓冲区没有可读字节,操作inputStream对象从新填充缓冲区 3、读取缓冲区

    public synchronized int read(byte b[], int off, int len)
        throws IOException
    {
        //检查流是否被关闭
        getBufIfOpen(); // Check for closed stream
        //如果需要读取的长度大于b的长度则抛出异常
        //如果需要读取的长度为0,直接返回0
        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        //n表示实际读取的字节数
        int n = 0;
        for (;;) {
            int nread = read1(b, off + n, len - n);
            if (nread <= 0)
                return (n == 0) ? nread : n;
            n += nread;
            if (n >= len)
                return n;
            // if not closed but no bytes available, return
            InputStream input = in;
            if (input != null && input.available() <= 0)
                return n;
        }
    }

    private int read1(byte[] b, int off, int len) throws IOException {
        //avail 剩余可读取的字节数
        int avail = count - pos;
        //缓冲区没有缓存数据
        if (avail <= 0) {
            /* If the requested length is at least as large as the buffer, and
               if there is no mark/reset activity, do not bother to copy the
               bytes into the local buffer.  In this way buffered streams will
               cascade harmlessly. */
            //如果需要读取的字节数大于缓冲区的长度,没有设置标记位
            //就直接调用InputStram in 对象读入数据,不使用缓冲区
            if (len >= getBufIfOpen().length && markpos < 0) {
                return getInIfOpen().read(b, off, len);
            }
            //填充缓冲区,操作InputStram in 对象读入数据到缓冲区
            fill();
            //重置缓冲区剩余可读字节数
            avail = count - pos;
            if (avail <= 0) return -1;
        }
        int cnt = (avail < len) ? avail : len;
        //从缓冲区读取数据到byte[] b,并返回实际读取的字节数
        System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
        pos += cnt;
        return cnt;
    }

这里最重要的方式是fill(),它调用InputStream in 对象一次性读入多个字节的数据存放到缓冲区。什么时候需要填充缓冲区呢?随着数据的读取pos会变大,当pos>=count时,说明缓冲区已经没用剩余可读数据了,需要调用 in 对象读入数据覆盖缓冲区,同时重置pos和count值。前面我们还提到了markpos,为了能够重复读取之前的数据,缓冲区需要保留markpos到marklimit 之间的数据。因此在我们覆盖缓冲区时,就必须要考虑到这个情况。

1、当没有设置markpos

当没有设置markpos,就不需要保留可重复读的数据,只需要将pos设置为0,再根据新读入的字节数重置count位置,这样就重置了缓冲区。

2、当设置了markpos,这里又要分几种情况:

      A:markpos >0 ,pos>=buffer.length,这说明当前缓冲区已经没有空余可读空间了,

          此时当markpos > 0 时 那么只需要丢弃markpos之前的数据,保留markpos 到pos之间的数据,count,pos = post-markpos, markpos=0。调整后pos到buffer.length之间就是可以填充数据的空间

   B:当markpos=0,pos>=buffer.length,buffer.length >= marklimit 时 可以得出在markpos标记后已经读取了超过marklimit 个字节,此时markpos已失效,直接将pos=0重置缓冲区,无需保留额外数据

   C:当marpos=0,buffer.length < marklimit,说明整个缓冲区都需要被保留,因此只能扩大缓冲区容量。

 private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            //没有设置标记对应情况1
            pos = 0;            /* no mark: throw away the buffer */
        else if (pos >= buffer.length)  /* no room left in buffer */
            对应情况2-A
            if (markpos > 0) {  /* can throw away early part of the buffer */
                int sz = pos - markpos;
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) {
                对应情况2-B
                markpos = -1;   /* buffer got too big, invalidate mark */
                pos = 0;        /* drop buffer contents */
            } else if (buffer.length >= MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("Required array size too large");
            } else {            /* grow buffer */
                对应情况2-C
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte nbuf[] = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                将buffer缓冲区引用原子的设置为nbuf
                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                    // Can't replace buf if there was an async close.
                    // Note: This would need to be changed if fill()
                    // is ever made accessible to multiple threads.
                    // But for now, the only way CAS can fail is via close.
                    // assert buf == null;
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        //调用in对象填充缓冲区
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

参考:https://blog.csdn.net/songwei128/article/details/23355045

https://blog.csdn.net/weixin_33862188/article/details/85631788

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值