美文网首页
聊一聊 BufferedInputStream 和 Output

聊一聊 BufferedInputStream 和 Output

作者: 一个追寻者的故事 | 来源:发表于2020-08-24 13:46 被阅读0次

一、BufferedInputStream

BufferedInputStream 的作用是什么?能够提高效率吗?

BufferedInputStream 是缓冲输入流。BufferedInputStream 的作用是为另一个输入流添加一些功能,
例如,提供“缓冲功能”以及支持“mark()标记”和“reset()重置方法”。BufferedInputStream 本质上是通过
一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()
读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据
被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置。

上边先来了一段总结,让大家有一段感性认识(来自网络,比我写得好)

输入流在默认情况下是不被缓冲区缓存的,也就是说,每个read的调用都会请求操作系统再分发一个字节。(摘自Java核心技术)

我们来看一下InputStream

public abstract class InputStream implements Closeable {
    public abstract int read() throws IOException; //从输入流读取一个字节

    //其余的方法是基于 read()变化的
    public int read(byte b[], int off, int len) throws IOException {
        //...
        try {
            for (; i < len ; i++) {
                c = read();    // 循环调用read(),从而获取字节数组
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
    //...
}

从上边的定义中可知, 一次读取一个字节数组 和 调用单字节read() n次获取字节数组 本质上并没有差异。除非具体的输入流有不同的实现,例如:FileInputStream

再来看一下 BufferedInputStream 的实现:

class BufferedInputStream extends FilterInputStream {
    protected volatile byte buf[];
    //读取单个字节
    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();    // buf 读取完毕,直接再从依赖的InputStream中读取字节数组,填充缓存
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;  //buf有的话就直接返回
    }
    //填充buf
    private void fill() throws IOException {
        //...
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }
    // 读取字节数组
    public synchronized int read(byte b[], int off, int len)
        throws IOException
    {
        // ...
        int n = 0;
        for (;;) {
            int nread = read1(b, off + n, len - n);  //读取字节数组
            // ...
        }
    }
    //根据条件,有可能直接从输入流读取,有可能先填充buf,再从buf中获取
    private int read1(byte[] b, int off, int len) throws IOException {
        int avail = count - pos;
        if (avail <= 0) {
            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;
    }
    //...
}

BufferedInputStream 通过装饰器模式,增强其它输入流的功能。看到这里我们可以得出一个结论,BufferedInputStream 的缓存功能并没有给普通输入流的效率带来什么大的变化,只是给使用者提供了方便的功能。(当然这句话有个前提,就是所包装输入流 的 read(byte b[], int off, int len) 是基于 int read() 实现)

我们写一段测试代码,实验一下:

//不带缓存
fun readFileNotBuffer2(byteInput: InputStream){
    val start = System.currentTimeMillis()
    var byte = byteInput.read()    // ByteArrayInputStream 
    while (byte != -1){
        byte = byteInput.read()
    }
    val end = System.currentTimeMillis()
    println("readFile NotBuffer2-during ${end - start} ms")
}

// 带缓存
fun readFileWithBuffer2(byteInput: InputStream){
    val start = System.currentTimeMillis()
    val input = byteInput.buffered()  // 构建基于ByteArrayInputStream 的 BufferedInputStream
    input.use {
        var byte = it.read()
        while (byte != -1){
            byte = input.read()
        }
        val end = System.currentTimeMillis()
        println("readFile WithBuffer2-during ${end - start} ms")
    }
}

fun main() {
    val buffer = File("pic.png").source().buffer()
    val byteInput = buffer.use {
        ByteArrayInputStream(it.readByteArray())
    }
    //构建的ByteArrayInputStrean都已经读取到内存中了
    byteInput.use {
        readFileWithBuffer2(it)
    }

    val buffer2 = File("pic.png").source().buffer()
    val byteInput2 = buffer2.use {
        ByteArrayInputStream(it.readByteArray())
    }
    //构建的ByteArrayInputStrean都已经读取到内存中了
    byteInput2.use {
        readFileNotBuffer2(it)
    }
}

看一下结果:(时间不稳定,使用BufferedInputStream没有明显优势)

readFile WithBuffer2-during 5
readFile NotBuffer2-during 2

readFile WithBuffer2-during 4
readFile NotBuffer2-during 13

readFile WithBuffer2-during 3
readFile NotBuffer2-during 5

但是当 BufferedInputStreamFileInputStream 搭配时可以很好提高效率,我们看一段测试代码

//不带缓存
fun readFileNotBuffer(){
    val start = System.currentTimeMillis()
    val input = File("pic.png").inputStream()    //直接使用FileInputStream
    input.use {
        var byte = it.read()
        while (byte != -1){
            byte = input.read()
        }
    }
    val end = System.currentTimeMillis()
    println("readFile NotBuffer-during ${end - start} ms")
}

// 带缓存
fun readFileWithBuffer(){
    val start = System.currentTimeMillis()
    val input = File("pic.png").inputStream().buffered() //BufferedInputStream包装了FileInputStream
    input.use {
        var byte = it.read()
        while (byte != -1){
            byte = input.read()
        }
    }
    val end = System.currentTimeMillis()
    println("readFile WithBuffer-during ${end - start} ms")
}

fun main() {
    readFileWithBuffer()
    readFileNotBuffer()
}

运行结果:(很稳定,基本上都是5 到 10倍的效率提升)

readFile WithBuffer-during 7 ms
readFile NotBuffer-during 67 ms

为什么和FileInputStream搭配就会有很好的效率提升呢?

看那一下 FileInputStream 实现

class FileInputStream extends InputStream{
    public int read() throws IOException {
        return read0();  //本地方法
    }
    private native int read0() throws IOException;  //本地方法
    //读取字节数组,也是基于native方法
    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);    //本地方法
    }
    //本地方法
    private native int readBytes(byte b[], int off, int len) throws IOException;
}

从代码我们可以知道,FileInputStream 的 read() 和 read(byte b[], int off, int len)都是基于 native 方法实现,read对于字节数组的获取已经不是基于read()单字节的实现。有了这层保证,我们知道配合BufferedInputStream的使用,可以极大减少对于io的访问次数,直接从内存获取数据,效率肯定得到很好提升。

总结:聊了这些应该对于BufferedInputStream的作用,以及BufferedInputStream对效率的提升有没有作用,也有了一个清晰的认识。还是建议用上Buffered,就算没有效率提升,毕竟没有什么坏处。

二、OutputStream

public abstract class OutputStream implements Closeable, Flushable {
    //像输出流 写如单个字节
    public abstract void write(int b) throws IOException;
    // write(byte b[], int off, int len) 也是基于 write(int b)单字节写入实现
    
    // flush 输出流,强制缓存的字节写入输出流
    public void flush() throws IOException {}
}

OutputStream定义了输出流的行为规范,具体的功能需要子类去实现。flush的目的是想对输出流有一个约束,就是尽量不要每次都通过write把字节写出到输出流,而是先缓存下来,然后在何时的时间通过一次性写入OutputStream。其实说,这个有没有用呢,得看具体的输出流实现,如果write(byte b[]) 是 基于 write(int b)实现的,那基本上效率也并没有太大的变化。

看一下FileOutputStream 的实现:

class FileOutputStream extends OutputStream{
    private native void write(int b, boolean append) throws IOException;
    public void write(int b) throws IOException {
        write(b, append);
    }

    public void write(byte b[], int off, int len) throws IOException {
        writeBytes(b, off, len, append);
    }
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;
}

FileOutputStream 针对单字节 和 字节数组的输出都做了处理,这时候通过先缓存在 buffer里,然后在合适的时候,再写出到输出流效率肯定会提升很多,因为毕竟 io 次数大大减少了。

看一下 BufferedOutputStream 的实现就一目了然了。

class BufferedOutputStream extends FilterOutputStream {
    protected byte buf[];
    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();
        }
        buf[count++] = (byte)b;
    }
    //flush
    public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }

    /** Flush the internal buffer */
    private void flushBuffer() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);
            count = 0;
        }
    }

}

相关文章

  • 聊一聊 BufferedInputStream 和 Output

    一、BufferedInputStream BufferedInputStream 的作用是什么?能够提高效率吗?...

  • 和老公聊一聊

    今天跟老公聊天,说到跟孩子相关的事,我们一起讨论研究他之前沉迷于手机,未对孩子和我太多上心的问题。 关于他和久久的...

  • 和儿子聊一聊

    晚饭后,女儿回自己房间写作业,我们三口坐到了沙发上,在儿子讲完一个笑话之后,我们言归正传。 “儿子,妈妈问你一个问...

  • 和团委君聊一聊

    这个即将到来的五一你想要去哪里玩呢QwQ

  • 和年长的人聊一聊

    时间久了,越来越焦虑,昨天晚上找了学姐聊一聊,她曾经是我们的班助,现在在深圳,也算是年长我五岁了。聊一聊,终究是有...

  • 聊一聊读书和写作

    Sinny说:今天这一篇关于读书的碎碎念。公众号开通快两个月了,一步一步的拿到原创标识,拿到赞赏功能,也慢慢的开始...

  • 和自己随机聊一聊

    碎碎念现在的心情 年纪轻的时候以为有永远或者永恒 现在想起来好想大笑 真的是太年轻了没见过世面 哈哈哈 等到再大一...

  • 和家长朋友聊一聊

    家长朋友,今天以这种方式让您知道孩子近段的状态,其目的就是想让您了解这一学期孩子的收获。通过与孩子四个月的相处,我...

  • 和小宝聊一聊规划

    小宝昨天晚上又和我聊了聊最近三五年的规划,大致的路线:去邮轮工作,然后考研,考个教师编制,要个许阳。 还准备带着我...

  • 和婷芝聊一聊

    近期心里有些忙乱,主要还是集中在工作上面。 1、互联网的35周岁定论? 2、未来的发展计划如何走? 3、如果在这家...

网友评论

      本文标题:聊一聊 BufferedInputStream 和 Output

      本文链接:https://www.haomeiwen.com/subject/dzkujktx.html