[Java Performance] 緩衝I/O(Buffered I/O),javaperformance
緩衝I/O(Buffered I/O)
InputStream.read()以及OutputStream.write()操作的對象是單個位元組。根據它們訪問的資源的不同,使用這些方法可能會相當慢。
比如在使用FileInputStream.read()時,速度會慢的令人髮指。因為每次調用都會訪問作業系統的核心去拿到1個位元組的資料。在現代的作業系統中,核心往往會使用緩衝I/O實現,因此這個操作還不至於每次調用時會觸發一次磁碟讀取操作。但是緩衝區畢竟是在核心中的,所以每次調用該方法還是意味著會發生一次昂貴的系統調用來擷取到核心I/O緩衝區中的1個位元組。
對於寫資料也是一樣的。每次調用FileOutputStream.write()方法都會將1個自己的資料存放區到核心的緩衝區中。最終當檔案被關閉或者調用flush方法的時候,核心才會將緩衝區的內容寫入到磁碟。
對於基於檔案的位元據I/O(File-based Binary Data I/O),務必使用BufferedInputStream或者BufferedOutputStream對底層的檔案位元組流進行一次封裝。
對於基於檔案的字元資料I/O(File-based Character Data I/O),務必使用BufferedReader或者BufferedWriter對底層的檔案字元流進行一次封裝。
實際上,以上的最佳實務不僅僅只限於檔案I/O,對於其它各種類型的I/O幾乎都適用。比如通過Socket得到的位元組流(通過getInputStream()和getOutputStream()擷取),在使用它們之前,也務必使用緩衝過濾流(Buffering Filter Stream)對它們進行封裝。
然而,還是有特例的。當使用ByteArrayInputStream和ByteArrayOutputStream類型時,不要對它們使用緩衝過濾流。這兩種類型會在記憶體中設定一片地區作為緩衝區,所以在為它們設定緩衝過濾流時,相當於會讓資料被拷貝兩次,以ByteArrayInputStream為例:
- 從核心緩衝區到緩衝過濾流的緩衝區
- 從緩衝過濾流的緩衝區到ByteArrayInputStream
當有其它過濾流(Filtering Stream)參與進來時,是否使用緩衝過濾流就需要具體問題具體分析了。比如在一個序列化的例子中:
private void writeObject(ObjectOutputStream out) throws IOException { if (prices == null) { makePrices(); } out.defaultWriteObject();}protected void makePrices() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(prices); oos.close();}
儘管ObjectOutputStream一次只會發送一個位元組到下一個Stream,但是當下一個Stream就是最終的ByteArrayOutputStream時,使用BufferedOutputStream就沒有意義了。這隻會增加資料的拷貝次數,從而導致效能的下降。
但是當在ByteArrayOutputStream和ObjectOutputStream之間還存在其它的過濾流,也許過濾緩衝流就能派上用場了。比如當需要使用一個壓縮過濾流將位元組數組進行壓縮時:
private void writeObject(ObjectOutputStream out) throws IOException { if (prices == null) { makeZippedPrices(); } out.defaultWriteObject();}protected void makeZippedPrices() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream zip = new GZIPOutputStream(baos); BufferedOutputStream bos = new BufferedOutputStream(zip); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(prices); oos.close(); zip.close();}
以上在GZIPOutputStream和ObjectOutputStream之間添加了一個BufferedOutputStream,這樣做能夠提高效能的原因是:當GZIPOutputStream的操作對象是一塊資料時的效能會高於操作對象是一個位元組時。
當使用Encoder/Decoder流來轉換位元組資料和字元資料時,使用緩衝過濾流對它們進行封裝,也能夠獲得更好的效能。
下表是一組在進行帶有壓縮的序列化/還原序列化時,是否使用緩衝過濾流對最終時間的影響:
| 操作 |
序列化時間 |
還原序列化時間 |
| 無緩衝的壓縮/解壓縮 |
60.3s |
79.3s |
| 有緩衝的壓縮/解壓縮 |
26.8s |
12.7s |
可見,當向GZIPOutputStream和ObjectOutputStream之間添加一個BufferedOutputStream後,效能的提升是多麼地明顯。
總結
InputStream.read()以及OutputStream.write()的效能比較低,因為它們只是操作了一個位元組。
- 在對檔案流,Socket流,壓縮流和字元編碼流進行操作時,確保使用了緩衝過濾流來封裝它們。
java中自訂的數組緩衝區與帶BufferedInputStream 或者BufferedWriter有什不同
個人覺得是這些做法反映了對資料處理方法的進步和發展。
用直接byte[]是最基礎的或者叫最原始的方法,等於從0開始,自己發明輪子。
各種Stream,Reader,Writer,Scanner,Printer等屬於對一些常規I/O處理的便捷化,也是在實踐中形成的針對特定情況的較佳方案。有了常用的一些輪子,運東西方便。各種Stream屬於Java的標準IO庫。
到了Java 5之後出現了新IO庫(NIO)。針對現在大容量檔案、大資料量讀寫、並行多線程讀寫提供了進一步的方便性工具,等於直接提供有輪子的車讓你搬東西。
具體用哪個好,取決於具體場合,和你對基礎知識和工具的駕馭能力。
對於複雜電磁環境下的作戰,不能老重複發明輪子。
對於做日常小菜,殺雞也不宜用牛刀。
Writter和Reader是針對字元char組成的字串的
而基本的Stream是針對位元組byte組成的位元組流、位元組串。
java中BufferedInputStream類相比InputStream類,提高了輸入效率,增加了輸入緩衝區的功可以,解釋下,
這個緩衝區的概念比較抽象,其實這麼說就明白了
不帶緩衝的操作,每讀一個位元組就要寫入一個位元組,由於涉及磁碟的IO操作相比記憶體的操作要慢很多,所以不帶緩衝的流效率很低
帶緩衝的流,可以一次讀很多位元組,但不向磁碟中寫入,只是先放到記憶體裡。等湊夠了緩衝區大小的時候一次性寫入磁碟,這種方式可以減少磁碟操作次數,速度就會提高很多