缓冲字节流BufferedInputStream的使用及原理分析

我们平时常常会对文件进行读取操作,如使用FileInputStream进行读取操作,则效率很低.为此我们可以使用缓冲字节流BufferedInputStream来操作,读取的效率会有很大的提升.在此我们介绍如何使用BufferedInputStream及分析其工作的原理.

一.使用介绍:

1.1定义:

BufferedInputStream是高级流,不能直接对文件进行操作,只有低级流才能直接与文件相连,所以需套接一个低级流,例如:

FileInputStream fis = new FileInputStream("test.txt");

BufferedInputStream bis = new BufferedInputStream(fis);

 

1.2读取文件:

读取时,我们一般使用read()方法循环的方式读取,如读取到了文件末尾,则read()方法会返回-1,例如:

int len = -1;
while((len=bis.read())!=-1){
    System.out.println(len);
}

通过循环读取的方式,可以将文件读取完毕.

二.原理分析:

 通过以上内容了解了BufferedInputStream的使用方式,那其工作原理是如何的呢?下面将通过BufferedInputStream的源码分析来了解其工作的原理.

2.1首先分析下BufferedInputStream的属性和构造函数:

属性如下:

//默认的缓冲大小8k
private static int DEFAULT_BUFFER_SIZE = 8192;
/*最大的缓冲大小Integer.MAX_VALUE - 8,减8是由于虚拟机中在数组中保留了一些头信息*/
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
//定义数据存储的缓冲字节数组
protected volatile byte buf[];
//原子属性更新器,用来保证对buf进行原子更新
private static final
        AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater =
        AtomicReferenceFieldUpdater.newUpdater
        (BufferedInputStream.class,  byte[].class, "buf");
//buf字节数组中实际数据的大小
protected int count;
//开始读取的位置
protected int pos;
//记录最后一次开始读取的位置
protected int markpos = -1;
//允许的最大提前读取量
protected int marklimit;

构造函数如下:

//以InputStream作为参数,缓冲区大小默认8k
public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
}

//自定义缓冲区大小
public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
}

2.2方法分析:

2.2.1.read()方法分析:

//无参数的read()方法
public synchronized int read() throws IOException {
        //如果开始读取的位置大于或等于缓冲区实际大小
        if (pos >= count) {
            //则填充缓冲区
            fill();
            //填充之后,读取位置还是大于或等于缓冲区实际大小,则读取完毕,返回-1
            if (pos >= count)
                return -1;
        }
        //返回缓冲区中的第一个字节
        return getBufIfOpen()[pos++] & 0xff;
}

 2.2.2.read1(byte[] b, int off, int len)方法分析:

此方法可以自定义的字节数组,以及开始读取的位置和实际读取的长度,源码如下:

private int read1(byte[] b, int off, int len) throws IOException {
     //可读取的大小 
        int avail = count - pos;
        //如果可读取大小小于或等于0
        if (avail <= 0) {
             //如果len大于或等于缓冲区大小且标记位置小于0,则按照给定的长度读取
             if (len >= getBufIfOpen().length && markpos < 0) {
                 return getInIfOpen().read(b, off, len);
             }
            //否则按照默认大小读取
            fill();
             avail = count - pos;
             //如果可读取大小小于或等于,则读取完毕,返回-1
             if (avail <= 0) return -1;
         }
         //取avail和len之间的较小值
         int cnt = (avail < len) ? avail : len;
         //将缓冲区的字节数组从pos位置开始,长度为cnt的内容复制到b字节数组中off开始的位置
         System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
         //读取位置增加cnt
         pos += cnt;
         //返回读取的长度
         return cnt;
}

2.2.3.read(byte b[], int off, int len)方法分析:

public synchronized int read(byte b[], int off, int len)
        throws IOException
    {
        //检查流是否关闭
        getBufIfOpen(); // Check for closed stream
       //通过"|"运算确保off,len大于或等于0,b.length大于或等于off+len
        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        int n = 0;
        //无限循环,只要剩余的内容满足长度len,则保证每次能读取到的长度是len
        for (;;) {
            //读取len长度字节
            int nread = read1(b, off + n, len - n);
            if (nread <= 0)
                return (n == 0) ? nread : n;
            //因为缓冲字节数组的长度是8192,假设len是800,则读取10次之后,缓冲字节剩余的长度是192,则第一次读取的长度就是192,n也就是192,此时,不会返回,会再次循环.再次循环时,则先将填充缓冲区,再读取剩余的608,读取到了800之后,则返回800的长度.
            n += nread;
            if (n >= len)
                return n;
            // if not closed but no bytes available, return
            InputStream input = in;
            //如果输入流中缓冲去可用的大小小于或等于0,则返回n
            if (input != null && input.available() <= 0)
                return n;
        }
}

2.2.4.fill()方法分析:

fill()方法是用于读取数据并填充缓冲区.

private void fill() throws IOException {
        //检查流是否关闭
        byte[] buffer = getBufIfOpen();
        //判断标记位置小于0
        if (markpos < 0)
            //设置位置为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;
            }
        //设置大小为pos
        count = pos;
        //读取长度为默认大小的数据到缓冲区,并返回读取的长度
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            //将count设置为长度大小
            count = n + pos;
} 

猜你喜欢

转载自www.cnblogs.com/allenjing/p/11330865.html