FileInputStream 与 BufferedInputStream的比较

本文对比分析了FileInputStream自带的缓存与BufferedInputStream自定义缓冲区在不同大小下的效率,指出当缓冲区小于8192字节时,BufferedInputStream表现更优。通过实例代码和源码解析,揭示了装饰者模式在性能提升中的作用。
摘要由CSDN通过智能技术生成

先介绍一下为啥比较他俩,因为BufferedInputStream是FileInputStream 的装饰者,但是我发现FileInputStream 中也是有缓存的,所以我就好奇装饰者起什么作用呢,这就是我写这篇文章的原因。

先说结论,当缓冲区的大小比8192小的时候,BufferedInputStream的效率更好。

发现是BufferedInputStream有一个默认为8192字节的缓冲区,当自定义的缓冲区小于8192的时候,默认一次性从硬盘中读取8192字节到内存中,然后每次只按自定义的缓冲量返回数据,性能好在了减少了读取硬盘的次数。

BufferedInputStream的示意图

 FileInputStream 示意图

示例

public class FileOperator {

    /** buffer size in bytes */
    final static int BUFFER_SIZE = 100;

    /**
     * copy file using FileInputStream & FileOutputStream

     * @param src copy from
     * @param dest copy to

     * @return;
     */
    public static void copyWithFileStream(File src, File dest){
        FileInputStream input = null;
        FileOutputStream output = null;

        try {
            input = new FileInputStream(src);
            output = new FileOutputStream(dest);

            byte[] buffer = new byte[BUFFER_SIZE];
            int copySize;

            while ((copySize = input.read(buffer)) > 0){
                output.write(buffer, 0, copySize);
                output.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                input.close();
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * copy file using BufferedInputStream & BufferedOutputStream

     * @param src copy from file
     * @param dest copy to file

     * @return;
     */
    public static void copyWithBufferedStream(File src, File dest){
        BufferedInputStream bufferedInput = null;
        BufferedOutputStream bufferedOutput = null;
        try {
            bufferedInput = new BufferedInputStream(new FileInputStream(src));
            bufferedOutput = new BufferedOutputStream(new FileOutputStream(dest));

            byte[] buffer = new byte[BUFFER_SIZE];
            int copySize;

            while ((copySize = bufferedInput.read(buffer)) > 0){
                bufferedOutput.write(buffer, 0, copySize);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedInput.close();
                bufferedOutput.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

class FileOperatorTest{
    public static void main(String args[]){
        File src = new File("test.txt");
        File dest = new File("copyTest.txt");

        try {
            if (!dest.exists()){
                dest.createNewFile();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //test copy using FileStream
        int startTime = System.currentTimeMillis();

        FileOperator.copyWithFileStream(src, dest);

        int endTime = System.currentTimeMillis();
        System.out.println("Copy file using FileStream takes : " + (endTime - startTime) + " ms.");

        //test copy using BufferedStream
        startTime = System.currentTimeMillis();

        FileOperator.copyWithBufferedStream(src, dest);

        endTime = System.currentTimeMillis();
        System.out.println("Copy file using BufferedStream takes : " + (endTime - startTime) + " ms.");
    }
}

【运行结果】
测试文件大小约为900M,以下是在设定BUFFER_SIZE为不同值时的一次执行结果:

BUFFER_SIZE = 100
Copy file using FileStream takes: 42680 ms.
Copy file using BufferedStream takes: 2407 ms.

BUFFER_SIZE = 8192
Copy file using FileStream takes: 1689 ms.
Copy file using BufferedStream takes: 1654 ms.

BUFFER_SIZE = 1000000
Copy file using FileStream takes: 957 ms.
Copy file using BufferedStream takes: 929 ms.

关键源码分析如下

BufferedInputStream的源码

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

getBufIfOpen方法中的buf是8192,所以buffer =8192。其中类实例化的时候,buf为默认长度

private byte[] getBufIfOpen() throws IOException {
        byte[] buffer = buf;
        if (buffer == null)
            throw new IOException("Stream closed");
        return buffer;
    }

//构造函数设置默认长度
public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

默认长度

private static int DEFAULT_BUFFER_SIZE = 8192;

 read1方法是重点

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

count=0,pos=0 所以avail =0

当自定义的长度为100时,所以len=100 len >= getBufIfOpen().length(8192)的条件是不满足的,所以不进入结构体,接下来调用fill方法

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

其中markpos =-1,所以pos=0

pos >= buffer.length的条件是不满足的,所以执行到count=pos=0

int n = getInIfOpen().read(buffer, pos, buffer.length - pos);

private InputStream getInIfOpen() throws IOException {
        InputStream input = in;
        if (input == null)
            throw new IOException("Stream closed");
        return input;
    }

这个就是调用了inputsteram的read方法,就是按缓存量为8192的进行读取的

buffer有了数据,由于是同一个引用,所以buf缓存中也有了数据

n=8192,count=8192

回到read1方法 avail = count - pos;

avail=8192,接下来的三元运算 cnt=100

System.arraycopy(getBufIfOpen(), pos, b, off, cnt);

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

其中的getBufIfOpen()就是取buf字节数组,就是有了数据的8192的字节数组,就是将buf的前100个字节复制到b中就是自定义的字节数组中

然后pos按len长度自增,返回cnt=100

返回到read方法就不解释了,挺简单了

上面例子搬运自下面的大佬

FileInputStream 与 BufferedInputStream 效率对比 - 戴仓薯 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值