1. 程式人生 > >BufferedInputStream 底層原理解析

BufferedInputStream 底層原理解析

前言

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讀取完後,會再次用底層輸入流填充緩衝區陣列。