BufferedInputStream源码分析

inputStream的在BufferedInputStream中使用到的方法


1、read()
2、skip()
3、available()




浅复制
BufferedInputStream内部维护一个缓冲字节数组,
当调用read1(byte[] b)方法时,核心原理是将内部缓冲字节数组的内容,浅复制到方法参数的目标字节数组中。
这样做的好处是,避免了大量的磁盘IO,因为原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,那么下一次读取一般情况下就直接可以从缓冲区读取,减少了磁盘IO




read1(byte[] b, int off, int len)

方法是非贪婪读取,最多只从底层字节流读取一次

处理方式简称为:缓冲区没剩先填充,有剩直接读,读取有偏差,不一定按目标。

处理方式如下:

如果内部缓冲字节数组的剩余字节数为0:

      额外判断:如果目标读取字节数大于内部缓冲字节数组的长度 ,直接从底层字节流读取目标读取字节数,给目标字节数组。这样做的好处是,不用经过填充内部缓冲字节数组的中间过程,实现流的平滑降级;

       调用fill()方法填充内部缓冲字节数组。并再次检查填充后缓冲区的剩余字节数是否大于0,从而判断是否达到文件尾。

如果内部缓冲字节数组的剩余字节数大于0:

1、如果内部缓冲字节数组的剩余字节数,大于用户的目标读取字节数,按照目标读取字节数的大小浅复制给目标字节数组;
2、如果内部缓冲字节数组的剩余字节数,小于用户的目标读取字节数,按照剩余字节数的大小浅复制给目标字节数组;
/**
     * Read characters into a portion of an array, reading from the underlying
     * stream at most once if necessary.
     */
    private int read1(byte[] b, int off, int len) throws IOException {
        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. */
            if (len >= getBufIfOpen().length && markpos < 0) {
                return getInIfOpen().read(b, off, len);
            }
            fill();
            avail = count - pos;
            if (avail <= 0) return -1;
        }
        int cnt = (avail < len) ? avail : len;
        System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
        pos += cnt;
        return cnt;
    }


read(byte b[], int off, int len)

方法是贪婪读取,通过重复调用底层字节流的read()方法,尽可能的读取最多的数据。

实现逻辑是通过死循环调用read1(byte[] b)方法,直到遇到以下条件退出:

1、已经完成用户要求的目标读取字节数;
2、底层字节流的read()方法返回-1,表示到达文件末尾,或者因为底层字节流的available()方法返回0。
 public synchronized int read(byte b[], int off, int len)
        throws IOException
    {
        getBufIfOpen(); // Check for closed stream
        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        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;
        }
    }



fill方法

需求:

 如果没有mark标记,凡是读过的数据都可以丢弃,即pos之前的数据都可以丢弃 ;
 如果有mark标记,markpos之前的数据才可以丢弃;
丢弃不需要的数据后,将缓冲字节数组填满。

实现:

a.  如果没有markpos,直接重置缓冲区,即将pos设为0。这种方式下,缓冲区永远不会有达到极限,抛出内存溢出异常的时候。然后从底层输入流中读取数据,填满缓冲区。

b.  如果存在markpos,当pos未达到缓冲区的末端,说明缓冲区还有剩余空间:从底层输入流中读取数据,填满缓冲区。

c.  如果存在markpos,当pos已经达到缓冲区的末端,说明缓冲区没有剩余空间,它的处理方式有以下几种:
    1、如果markPos大于0,对mark位置到pos位置的数据予以保留,而将mark位置之前的数据丢弃,通过浅复制将缓冲区中markpos至pos部分的移至缓冲区头部,并将markPos重置为0;
    2、由第一步可见,marrkPos会等于0。如果markPos等于0,进行缓冲区扩容。创建新的缓冲区,长度为当前pos的2倍长(可以预估新的缓冲区长度>=旧的缓冲区的长度的2倍),将旧的缓冲区的内容复制给新的缓冲区。

   3、由第二步可见,缓冲区不断扩容,总会有达到极限的时候,当缓冲区的长度大于规定的最大缓冲区大小(MAX_BUFFER_SIZE)时,抛出内存溢出异常。另外,当缓冲区的长度大于用户规定的markLimit时,则认定该mark无效,重置缓冲区。

    最后,才从底层输入流中读取数据,填满缓冲区。

private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* no mark: throw away the buffer */
        else if (pos >= buffer.length)  /* no room left in buffer */
            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) {
                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 */
                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);
                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;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos); //该方法将缓冲区填满
        if (n > 0)
            count = n + pos;
    }





skip(long n)方法

表示跳过多少字节。通过调用底层字节流的skip()方法完成。该方法的实现为尽量原则,不保证一定跳过目标字节数。

处理方式简称为:缓冲区没剩先填充,有剩就skip,skip有偏差,不一定按目标。

处理方式如下:
1、如果缓冲字节数组没有剩余字节数,处理方式如下:

     如果没有mark标记,则直接从底层字节流中skip,结束。

     如果有mark标记,调用fill()方法填充缓冲区。并再次检查填充后缓冲区的剩余字节数是否大于0,从而判断是否达到文件尾。剩余字节数大于0,就可以走第2步。

2、缓冲字节数组有剩余字节数,将pos前移用户的目标跳过字节数,或者前移缓冲区的剩余字节数。
public synchronized long skip(long n) throws IOException {
        getBufIfOpen(); // Check for closed stream
        if (n <= 0) {
            return 0;
        }
        long avail = count - pos;

        if (avail <= 0) {
            // If no mark position set then don't keep in buffer
            if (markpos <0)
                return getInIfOpen().skip(n);

            // Fill in buffer to save bytes for reset
            fill();
            avail = count - pos;
            if (avail <= 0)
                return 0;
        }

        long skipped = (avail < n) ? avail : n;
        pos += skipped;
        return skipped;
    }



available()方法

返回目前可用的字节数。等于底层字节流中可用的字节数+缓冲区中的剩余字节数。

public synchronized int available() throws IOException {
        int n = count - pos;
        int avail = getInIfOpen().available();
        return n > (Integer.MAX_VALUE - avail)
                    ? Integer.MAX_VALUE
                    : n + avail;
    }


ps:在上述文字定义中,缓冲区没有剩余字节数和缓冲区没有剩余空间,两者是不一样的。

剩余字节数的计算公式为:count - pos

剩余空间的计算公式为:buffer.length - pos

参考:BufferedInputStream源码分析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值