BufferedInputStream 底层原理解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MakeContral/article/details/85318673

前言

RMQ中为了实现高性能在IO上做了很多优化,在阿里中间件性能挑战赛也可以看到很多大神们在IO上花了很多功夫去分析优化,这篇主要来解析一下BufferedInputStream的实现原理,看看它在IO上做了哪些优化。

误区

看到很多文章是这么描述BufferedInputStream性能高的原因:BufferedInputStream 将数据先保存在了缓存区,从而减少磁盘IO操作次数,提高IO效率。这么说我觉得不够严谨。

第一点,的确BufferedInputStream 是将数据保存了缓存区,但是减少磁盘IO并不是BufferedInputStream来做的,而是OS来做的。OS根据局部性原理,会预读部分的数据到内存缓存区(pagecache),这样下次IO如果读取数据在缓存命中了,就不需要等待磁盘的寻址,而是直接返回数据。

那么BufferedInputStream相比于FileInputStream优化了那块性能呢?答案是系统调用。

系统调用

那么这里就来解释什么是系统调用了。我觉得在这里你可以认为是一次用户态和内核态的切换,以及CPU的一次参与。

看读取一个文件实际的过程如下:

1、系统调用 read() 产生一个上下文切换:从 user mode 切换到 kernel mode,然后 DMA 执行拷贝,把文件数据从硬盘读到一个 kernel buffer 里

2、数据从 kernel buffer 拷贝到 user buffer,然后系统调用 read() 返回,这时又产生一个上下文切换:从kernel mode 切换到 user mode。

用一个例子来理解一下FileInputStream:

1.假设我要从一个文件中读取4个字节的内容,user mode 切换到 kernel mode,系统根据局部性原理,通过 DMA 会读入12个字节到 kernel buffer 中。这里涉及到一次上下文切换,以及一次数据的拷贝(DMA执行,CPU参与不大)。

2.然后再将 kernel buffer 中的4个字节的数据拷贝到 user buffer 中,kernel mode 切换到 user mode 。注意这个时候 kernel buffer 还保留了 8 个字节的数据。这里涉及到一次上下文切换,以及一次数据的拷贝(CPU执行)。

3.下次 user mode 中想再读取4个字节,那么就不需要等待DMA的拷贝,而是直接从 kernel buffer 中将剩下的字节拷贝到user buffer中。IO效率的确提高了,但是这部分的优化是OS来做的。

但是BufferedInputStream 认为还有优化的空间在,它认为 user mode 想再读取字节的时候(上述的3),把数据从 kernel buffer 拷贝到 user buffer中,这个过程也涉及到了一次上下文切换,而且需要CPU来进行拷贝。可不可以在 kernel buffer 把数据拷贝到 user buffer 的时候,多拷贝一点到 user buffer 中呢?

同样的例子看看BufferedInputStream 读取的过程:

1.和FileInputStream一样。
2.虽然我需要读4个字节,但是我会将 kernel buffer 中拷贝8个字节到 user buffer 中。 kernel buffer这个时候还剩下4个字节, user buffer有8个字节,其中4个字节user mode还没使用过。

上面两步和FileInputStream唯一区别就是多拷贝了点数据到user buffer 中。

3.当我想再次读取4个字节的时候,因为数据已经在user buffer中了,我不需要上下文切换,而且可以让CPU执行更重要的事情了。

现在可以清楚了BufferedInputStream 为什么可以更快了把,下面用源码来证明上述的说法。

源码分析

看一下BufferedInputStream 的read方法:

public synchronized int read() throws IOException {
    if (pos >= count) {
        fill();
        if (pos >= count)
            return -1;
    }
    return getBufIfOpen()[pos++] & 0xff;
}

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

可以看到返回的数据的时候,是从一个数组返回的!!这个数组就是为了多保留预读进来的数据。当程序读取一个或多个字节时,可直接从byte数组中获取,当内存中的byte读取完后,会再次用底层输入流填充缓冲区数组。

猜你喜欢

转载自blog.csdn.net/MakeContral/article/details/85318673