JDK源码(一):BufferedInputStream

学习更多源码,请关注微信公众号:jdkSpring ,或者微信扫一下二维码:

      BufferedInputStream是缓冲输入流,作用是为另一个输入流添加一些功能,比如缓冲输入功能以及支持mark和reset方法的能力。

      在创建BufferedInputStream时,会在内存中创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流一次性填充多个字节到该内部缓冲区。

      当程序需要读取字节时,直接从内部缓冲区中读取。当内部缓冲区中数据被读完后,会再次从包含的输入流一次性填充多个字节到该内部缓冲区。mark操作会记录输入流中的某个点,reset操作使得在从输入流中获取新字节之前,再次读取自最后一次mark操作后读取的所有字节。

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;

public class BufferedInputStreamDemo {

    public static void main(String[] args) throws Exception{

        /**
         * BufferedInputStream读取文件
         */
        BufferedInputStream bf = new BufferedInputStream(new FileInputStream(new File("D:\\jdk_s_d\\BufferedInputStream.txt")));
        byte [] bytes = new byte[1024];
        int j;
        while ((j = bf.read(bytes)) != -1) {
            String bs = new String(bytes, 0, j);
            System.out.println(bs);
        }
        bf.close();

        /**
         * FileInputStream和BufferedInputStream时间比较
         */
        FileInputStream fileInputStream = new FileInputStream(new File("D:\\jdk_s_d\\BufferedInputStream.txt"));
        int c;
        long fileStart = System.currentTimeMillis();
        while ((c = fileInputStream.read()) != -1) {}
        fileInputStream.close();
        long fileEnd = System.currentTimeMillis();
        System.out.println("FileInputStream: " + (fileEnd - fileStart));

        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("D:\\jdk_s_d\\BufferedInputStream.txt")));
        int b;
        long buffStart = System.currentTimeMillis();
        while ((b = bufferedInputStream.read()) != -1) {}
        bufferedInputStream.close();
        long buffEnd = System.currentTimeMillis();
        System.out.println("BufferedInputStream:" + (buffEnd - buffStart));
        /**
         * 输出结果:
         * FileInputStream: 19
         * BufferedInputStream:2
         */

    }
}

源码(jdk1.8)


public class BufferedInputStream extends FilterInputStream {

    //缓冲区大小,默认8192
    private static int DEFAULT_BUFFER_SIZE = 8192;

    /**
     * 分派给arrays的最大容量
     * 某些VM会在数组中保留一些头部字节,尝试分配这个最大存储容量,
     * 可能会导致array容量大于VM的limit,最终导致OutOfMemoryError,所以这里需要减8。
     */
    private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 存放数据的内部缓冲数组。
     * 当有必要时,可能会被另一个不同容量的数组替代。
     */
    protected volatile byte buf[];

    /**
     * 为缓冲区提供compareAndSet的原子更新器。
     * 这是很有必要的,因为关闭操作可以使异步的。我们使用非空的缓冲区数组作为流被关闭的指示器。
     * 该成员变量与buf数组的volatile关键字共同作用,实现了当在多线程环境中操作BufferedInputStream对象时,buf和bufUpdater都具有原子性。
     */
    private static final
        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
        AtomicReferenceFieldUpdater.newUpdater
        (BufferedInputStream.class,  byte[].class, "buf");

    /**
     * 缓冲区中的字节数。
     */
    protected int count;

    /**
     * 缓冲区当前位置的索引
     */
    protected int pos;

    /**
     * 最后一次调用mark方法时pos字段的值。值的范围是[-1, pos]。
     */
    protected int markpos = -1;

    /**
     * 调用mark方法后,在后续调用reset方法失败之前所允许的最大提前读取量。
     * markpos的最大值
     */
    protected int marklimit;

    /**
     * 获取输入流。
     * 判断输入流是否为null,如果为null,抛出异常,否则返回输入流。
     */
    private InputStream getInIfOpen() throws IOException {
        InputStream input = in;
        if (input == null)
            throw new IOException("Stream closed");
        return input;
    }

    /**
     * 获取缓冲区数组。
     * 检查缓冲区数组是否为null,如果为null,抛出异常,否则返回缓冲区数组。
     */
    private byte[] getBufIfOpen() throws IOException {
        byte[] buffer = buf;
        if (buffer == null)
            throw new IOException("Stream closed");
        return buffer;
    }

    /**
     * 创建一个缓冲区大小为DEFAULT_BUFFER_SIZE的BufferedInputStream。
     */
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    /**
     * 创建一个缓冲区大小为size的BufferedInputStream。     *
     */
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }

    /**
     * 填充缓冲区。
     */
    private void fill() throws IOException {
        //获取缓冲区数组
        byte[] buffer = getBufIfOpen();

        if (markpos < 0)//缓冲区没有被标记
            pos = 0;:
        else if (pos >= buffer.length)  //缓冲区被标记,且缓冲器已满。pos >= buffer.length说明缓冲区已满。
            if (markpos > 0) {  //缓冲区被标记且标记位置大于0,且缓冲器已满
                //获取被标记位置和缓冲区末尾之间的长度
                int sz = pos - markpos;
                //将buffer中从markpos开始的数据拷贝到buffer中(从位置0开始填充,填充长度是sz)
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                //将缓存区当前位置定位为sz
                pos = sz;
                //将标记位置定位为0
                markpos = 0;
            } else if (buffer.length >= marklimit) {//缓冲区被标记且标记位置等于0,且缓冲器已满,且缓冲区太大导致标记无效
                //将标记位置定位为-1
                markpos = -1;   
                //将当前位置定位为0
                pos = 0;        
            } else if (buffer.length >= MAX_BUFFER_SIZE) {//缓冲区被标记且标记位置等于0,且缓冲器已满,且缓冲区超出允许范围
                //抛出异常
                throw new OutOfMemoryError("Required array size too large");
            } else {//缓冲区进行扩容
                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)) {
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        //从输入流中读取数据,将缓冲区填满
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

    /**
     * 从缓冲区中读取下个字节
     *
     * 如果已到输入流末尾,返回-1。
     */
    public synchronized int read() throws IOException {
        //pos >= count,说明已经读到了缓冲区末尾,这时应该从输入轮流中读取一批数据来填充缓冲区
        if (pos >= count) {
            fill();
            //已经执行了填充数据的方法,缓冲区中还是没有数据可读,说明根本就没有数据被填充到缓冲区,这种情况一般是输入流已经没有数据可读,返回-1
            if (pos >= count)
                return -1;
        }
        //获取缓冲区,从缓冲区中读取下个字节
        return getBufIfOpen()[pos++] & 0xff;
    }

    /**
     * 从此字节输入流中给定偏移量off处开始将len个字节读取到指定的byte数组中。
     * 
     * @param   b     目标字节数组
     * @param   off   起始偏移量
     * @param   len   读取的最大字节数。
     * @return  读入b的总字节数,如果由于已到达流末尾而不再有数据,则返回-1。
     */

    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) {
                //直接从输入流中读数据到b中
                return getInIfOpen().read(b, off, len);
            }
            //填充缓冲区
            fill();
            //再次计算缓冲区中可读字节数
            avail = count - pos;
            //如果缓冲区中依然没有可读字节数,说明已经到达流末尾,返回-1
            if (avail <= 0) return -1;
        }
        //计算实际要读取的字节数
        int cnt = (avail < len) ? avail : len;
        //从缓冲区的pos位置将cnt个字节读取到b中
        System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
        //缓冲区当前位置+cnt
        pos += cnt;
        //返回实际读取的字节数
        return cnt;
    }

    /**
     * 从此字节输入流中给定偏移量处开始将各字节读取到指定的 byte 数组中。
     *
     * @param   b     目标字节数组
     * @param   off   起始偏移量
     * @param   len   读取的最大字节数。
     * @return  读入b的总字节数,如果由于已到达流末尾而不再有数据,则返回-1。
     */
    public synchronized int read(byte b[], int off, int len)
        throws IOException
{
        //检查输入流是否关闭
        getBufIfOpen(); 
        //检查参数是否合法
        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;
        }
    }

    /**
     * 跳过和丢弃此输入流或缓冲区中的n个字节。
     * 返回实际跳过的字节数
     */
    public synchronized long skip(long n) throws IOException {
        //检查输入流是否关闭
        getBufIfOpen();
        //如果参数不合法,返回0
        if (n <= 0) {
            return 0;
        }
        //缓冲区中可读的字节数
        long avail = count - pos;

        //如果avail<=0,即已经到达缓冲区末尾
        if (avail <= 0) {
            // 如果没有调用过mark方法,直接在输入流中跳过n个字节
            if (markpos <0)
                return getInIfOpen().skip(n);

            // 填充缓冲区
            fill();
            //重新计算可以读取的字节数
            avail = count - pos;
            //如果依然小于0,说明已经到了输入流末尾,没数据可读了,返回0
            if (avail <= 0)
                return 0;
        }

        //计算实际跳过的字节数
        long skipped = (avail < n) ? avail : n;
        //在缓冲区中跳过skipped个字节
        pos += skipped;
        //返回实际跳过的字节
        return skipped;
    }

    /**
     * 返回可以从此输入流读取(或跳过)、且不受此输入流接下来的方法调用阻塞的估计字节数。
     */
    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;
    }

    /**
     * 在此缓冲区中标记当前的位置。对reset方法的后续调用会在最后标记的位置重新定位此缓冲区,以便后续读取重新读取相同的字节。
     *
     * readlimit告知此缓冲区在标记位置失效之前允许读取的字节数。
     *
     * mark的一般约定是:如果方法markSupported返回true,那么缓冲区总是在调用mark之后记录所有读取的字节,并时刻准备在调用方法reset时(无论何时),
   * 再次提供这些相同的字节。但是,如果在调用reset之前可以从流中读取多于readlimit的字节,则不需要该流记录任何数据。
     *
     * @param   readlimit   在标记位置失效前可以读取字节的最大限制。
     * @see     java.io.BufferedInputStream#reset()
     */
    public synchronized void mark(int readlimit) {
        marklimit = readlimit;
        markpos = pos;
    }

    /**
     * 将此缓冲区重新定位到最后一次对此输入流调用mark方法时的位置。
     *

     * 如果创建流以后未调用方法mark,或最后调用mark以后从该流读取的
     * 字节数大于最后调用mark时的参数,则可能抛出IOException。
     * 
     * 如果未抛出这样的IOException,将此缓冲区重新定位到最后一次对
     * 此缓冲区调用mark方法时的位置。
     */
    public synchronized void reset() throws IOException {
        getBufIfOpen(); // Cause exception if closed
        if (markpos < 0)
            throw new IOException("Resetting to invalid mark");
        pos = markpos;
    }

    /**。
     * 是否支持mark和reset是特定输入流实例的不变属性。 
     */
    public boolean markSupported() {
        return true;
    }

    /**
     * 关闭此输入流并释放与该流关联的所有系统资源。
     * 关闭了该流之后,后续的read()、available()、reset()或 skip()调用都将抛出IOException。
     * 关闭之前已关闭的流不会产生任何效果。
     */
    public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();
                return;
            }
            // Else retry in case a new buf was CASed in fill()
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐楠_01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值