前言
Java I/O功能封裝的很好,使用起來很方便,就是剛開始學的時候,如果不瞭解裝飾器模式,會被他繁多的類給嚇到。用多了也就習慣了,而且現在有很多實用的封裝良好的實用類,可直接讀寫整個檔案。開發者不知道底層實現細節,也可以靈活使用,這是封裝的一大優點。但是,作為一名軟體開發人員,對其所使用的程式碼不能僅僅停留在熟悉功能特性上,最好對其實現原理也要有一定了解。
注:本文引用了部分外文內容,並根據自己的理解進行了翻譯,連線將在文末貼出。
緩衝處理、核心空間vs使用者空間
--------------------------------------外文引用內容Begin(已翻譯)----------------------------------------------------------------
緩衝,以及如何處理緩衝是所有IO的基本內容。術語"I/O"(輸入輸出)指的不過就是從緩衝區移入或移除資料。通常,程式執行I/O操作的方式是,向作業系統傳送請求,請求其填充自己的緩衝區(或者把自己緩衝區的內容寫出)。這就是I/O這個概念的全部內容。要實現這些傳輸操作,作業系統底層的實現非常複雜。但是在概念上,本文所要講述的內容則非常直白。
注意:User space和Kernel space 都屬於記憶體。記憶體分為兩個區,使用者區和系統區(核心區)。
上圖簡要展示了,塊資料如何從外部源頭(比如硬碟)移入到程式的記憶體空間的過程。首先,這個程式通過系統呼叫read(),請求填充自己的緩衝區。這將導致核心傳送一個命令到磁碟控制器,使其從磁碟中抓取資料。磁碟控制器通過DMA把資料直接寫入到核心空間緩衝區,這個過程不需要CPU干預。一旦磁碟控制器完成了填充資料的任務,核心就將資料從核心空間的臨時緩衝區轉移到程式指定的緩衝區內。
有一件事需要注意,核心會試圖緩衝或者說預載入一些資料,所以有可能程式所請求的資料已經在核心空間裡了。如果這樣的話,程式請求的資料,只需要從核心緩衝區拷貝一份即可。如果資料不在核心空間內,則在核心獲取資料到記憶體的過程中,此程式將被掛起。
--------------------------------------外文引用內容End(已翻譯)-------------------------------------------------------------------
從上述內容可知:
- Java的讀寫操作,底層由C/C++實現。而不是直接與OS接觸
- C/C++讀寫操作,需要OS服務
- 核心自帶緩衝,會過分載入
- 如果記憶體中沒有資料的緩衝,讀寫操作將阻塞當前執行緒(OS會幫你掛起執行緒)
DMA
DMA(Direct Memory Access,直接記憶體存取)是I/O裝置控制方式的一種。我個人認為它們的主要差別在於CPU的參與I/O控制的程度
I/O裝置控制方式有:
- 程式I/O方式——CPU需反覆檢查
- 中斷I/O方式——每完成一個位元組的讀寫,通知CPU
- DMA方式——每完成一個塊(多位元組)的讀寫,通知CPU
- I/O通道方式(暫不瞭解)
在DMA讀寫I/O裝置的時候,CPU不會被影響,它可以繼續執行。注意!這裡能繼續執行,指的是CPU可以繼續執行,而此I/O操作的執行緒已經被掛起,不參與CPU排程。I/O操作完成後,該執行緒才被喚醒,參與排程(加入就緒佇列,等待時間片)
系統呼叫
系統呼叫是應用程式間接呼叫OS函式的方式。C語言有提供與系統呼叫相對應的庫函式。這裡就是read、write。
BufferedXXStream
注意,對於Java來說,系統呼叫的開銷是比較大的。首先讀寫操作要觸發的是本地方法read0,readBytes,write0,writeBytes,這裡JNI需要一定開銷。還有就是每產生一個系統呼叫,就可能產生上千個機器指令,這種開銷是不容小覷的。所以,我們要嘗試減少系統呼叫。那有人就會問了,不行啊,我資料又不能缺斤少兩,少讀少寫肯定出問題,怎麼減少呼叫?這不是很好解決嗎,每次多讀寫一點,呼叫的次數不就少了嘛。而BufferedXXStream就是這麼用的,例如,BufferedInputStream的read無參方法只讀取一個位元組,而實際上BufferedInputStream預設讀取了8kb,這些資料用位元組陣列保留。
對了,如果對上圖,不是很理解,可以看看這張。
即執行時,有一個物件BufferedInputStream,其呼叫一次read()方法,資料保留到buf陣列中。