对Java Inputstream的一次采访
在學習java.io.*包的時候,InputStream那一群類很讓人反感,子類繁多就不用說,使用起來非常奇怪。我們想以緩存的方式從文件中讀取字節流。總要先創建一個FileInputStream,然后把它放入BufferedInputStream構造函數中去創建BufferedInputStream。完成這些工作后才能開始讀取文件。
為什么我們不能直接以緩存方式(BufferedInputStream)從文件中讀取數據呢?今天我帶著這樣的問題走進InputStream的家,對老人家進行一次采訪, 希望他能解決我心頭的疑惑。
我:老人家您好,我用InputStrem用了很久了, 一直以來都有一個問題困擾著我, 想請教您一下,為什么我們想以帶緩存的方式從文件中讀取字節流需要創建FileInputStream和BufferedInputStream兩個類,這太麻煩了,你看看人家Python, Ruby, 都是打開文件后直接就可以讀了,多方便啊!
?InputStream:年輕人,不要有情緒 ! 存在的就是合理的,? 這其實是一個很長的故事, 要從很久很久以前說起。那時候Java剛剛誕生,我也幸運地被創造出來。那時候帝國實施嚴格的計劃生育,我還沒有任何子孫,很是寂寞。一天,有一個叫小霍的年輕人找到了我。他說他要讓我飛黃騰達,子孫滿堂。
我:這么神?那您講講您和小霍之間的故事吧。
?
時光倒流回20年前,小霍初見年輕的InputStream。
小霍:InputStream先生你好,我是JAVA帝國計劃生育委員會的工作人員,組織上聽說你孤苦伶仃的,很是于心不忍,又給你爭取了好幾個生育名額, 讓你像Java集合框架那樣兒孫滿堂。
InputStream:真的嗎? 我知道爭取生育名額很不容易,那你說說,組織準備讓我生幾個?
小霍:你是IO的輸入類,負責讀取數據(字節流)。數據就是你的包裹,你一般從哪些渠道獲得包裹?
InputStream:文件,字節數組,StringBuffer,其它線程,對了還有已經被序列化的對象。
小霍: 那你先根據數據來源的渠道生5個孩子,老大叫FileInputStream,處理文件,老二叫ByteArrayInputStream,處理字節數組,老三叫StringBufferInputStream,處理StringBuffer,老四叫PipedInputStream,處理線程間的輸入流,老五叫ObjectInputStream,處理被序列化的對象。
InputStream:萬一有一個包裹里面有多個或者多種數據輸入流呢。
小霍:那就再生一個SequenceInputStream,處理一個包裹里有多種數據來源的業務。還有其它問題嗎?沒問題我就回單位了。帝國剛建立,我們計劃生育委員會掌管著全國的生育名額,我還忙著呢。你抓緊時間生孩子,有問題再找我。
InputStream:好咧,我這就關燈造人。
交流完畢后,小霍走了,我也抓緊時間把我6個孩子生了出來,為國家做貢獻。InputStream的6個孩子齊心協力,處理了JAVA早期很多的輸入業務。但是他們也面臨了新的問題。沒過多久。年輕的計生委員小霍再次找到了InputStream。
小霍:你那6個孩子都是能人啊,但是現在客戶抱怨他們的工作還不夠到位。
InputStream:我那6個孩子各司其職,工作勤勤懇懇,怎么還有人抱怨?
小霍:客戶嘛,都比較挑剔。他們抱怨你們讀取數據太慢了,尤其是你的老大FileInputStream每次讀數據都慢死了。好多客戶等待都超過了幾秒了,還沒把數據等回來。
InputStream:幾秒很慢嗎?
小霍:我們計算機都是以納秒計時的,所謂世間一秒鐘,機器已千年。那些客戶頭發都等白了。
InputStream:?讀數據慢能怪我嗎?這不是硬盤慢造成的嗎?
小霍:是,是硬盤造成的,我們想一個辦法讓用戶減少訪問硬盤的次數。比如建一個buffer怎么樣?用戶需要的數據先讓他們在buffer里面找,能找到就直接從buffer里返回,實在找不到再去硬盤里找。Buffer在內存里,內存可比硬盤快10萬倍呢(內存在隨機訪問的速度上是硬盤的10萬倍以上)。
InputStream:這辦法好。客戶抱怨的其他問題呢?
小霍:客戶想要的數據類型都是int, long, String這樣的JAVA基本類型,而你提供給他們的都是byte類型,還需要客戶自己進行類型轉換。客戶覺得麻煩。還有一個問題,Stream里面讀出來的數據就不能重新放回去,客戶想要一個功能,能把讀出來的數據再推回Stream里面。
InputStream:看來我得再生3個孩子:?擁有緩存的BufferedInputStream,把byte轉換成JAVA基本類型的DataInputStream和回寫數據到stream的PushbackInputStream。
小霍:老伙計,你糊涂了,不止3個。就拿FileInputStream 來說吧, 加上這三個功能就需要三個子類
Buffered + FileInputStream
Data+ FileInputStream
PushBack + FileInputStream
還有更大的問題,萬一某個特殊的客戶既想有數據回寫,又想要輸出int,long,String這樣的數據,還有要緩存。? ??或者說他們只要三個功能中的兩個,這樣組合起來, 又需要4個子類
Buffered + Data + FileInputStream
Buffered + PushBack + FileInputStream
Data + PushBack + FileInputStream
Buffered + Data + PushBack + FileInputStream
換句話說, 僅僅是FileInputStream, 就需要7個子類, 你還有其他5個孩子呢! 總共需要6 * 7 = 42個子類, 我估計客戶看到這么多子類,眼都花了。
InputStream:看來我們得想另外的辦法。要不然我的家族就要“類爆炸”了,再說讓我一下生那么多孩子,我也煎熬啊。
小霍:你玩過俄羅斯套娃沒?一個實心的娃娃被各種各樣娃娃外殼套著。一個實心娃娃先套一個學生的外殼,那么他就是學生了,如果我再在外面套一個運動員的殼,那么他就成了有運動員身份的學生。我們模仿這種形式,比如最里面的實心娃娃是處理文件讀取的FileInputStream,外面套一個BufferedInputStream的殼,那么這個套娃就是帶buffer的FileInputStream。如果再套一個DataInputStream,那么套娃就成了能輸出int這樣java?基本類型并且帶buffer的FileInputStream。搭配由客戶去決定,我們只需要提供套娃殼(新的3個功能類)和最里面的實心娃InputStream(InputStream的6個孩子)。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
InputStream:這很巧妙,那如何實現這樣一種設計呢?
小霍:這有2個關鍵點:
1. ?既然套娃中一定有實心娃娃,那么套娃的殼的類必須包含一個實心娃。比如BufferedInputStream里面要包含一個InputStream,我們把實心娃娃通過BufferedInputStream的構造函數放進去,當然DataInputStream和PushbackInputStream也一樣。
2. ?BufferedInputStream+實心娃娃InputStream組成的新套娃又可以當作DataInputStream的實心娃娃,那么我們讓這些套娃的外殼BufferedInputStream,DataInputStream,BufferedInputStream都繼承自InputStream類,這樣才能實現新組成的套娃又可以被另外的套娃殼嵌套。這3個套娃殼有著共同的特點都是裝飾實心娃娃,我們再在他們上層在抽象一個FilterInputStream,讓FilterInputStream繼承自InputStream,讓FilterInputStream里面包含一個實心娃娃InputStream。以后所有的裝飾類都從FilterInputStream繼承。
InputStream:這樣我也省事了,只需要再多生一個FilterInputStream,剩下的BufferedInputStream,DataInputStream,PushbackInputStream這樣的裝飾類都讓FilterInputStream去生了。
小霍:對,加上FilterInputStream,你就有7個孩子了,跟葫蘆娃一樣。前面6個哥哥都是和數據源有關,7弟就是用來裝飾6個哥哥。把數據源的InputStream類和裝飾的InputStream整合在了一起。
InputStream:而且對于BufferedInputStream,DataInputStream,PushbackInputStream來說,我還是爺爺,想著他們叫我爺爺的樣子,我心里就美滋滋的。
小霍:美得你,Java中無論多少次繼承都是子類父類關系,沒有爺爺這么一說。我把家譜給你。你兒子太多,我就畫ByteArrayInputStream,FileInputStream?和FilterInputStream的簡化版。
小霍:這樣的設計既避免了類爆炸,又可以讓用戶自己去搭配核心類和裝飾類。而且也滿足了一個重要的設計原則:開閉原則。這是指導思想。所謂開閉原則就是要對擴展開放,對修改關閉。我們的目標是允許類很容易地進行擴展,在不修改代碼的情況下就可以搭配新的行為。至于缺點嘛,在實例化的時候用戶不僅僅實例化核心類,還要把核心類包進裝飾者中。對于初次接觸IO類庫的人,無法輕易理解。
InputStream:這是一個好的設計模式,只是需要一些學習成本。以后要有人不理解這設計模式,我就把我和你之間的故事給他講一遍。
小霍:如此甚好。
?
回憶結束, 時光回到現在.
InputStream:故事講完了,這下你明白了嗎?
我:原來是這樣啊,為了防止類爆炸而采用了裝飾者模式, 要是按照我起初的想法,有一個專門的類來處理我的InputFileStream+BufferedInputStream,那InputStream您早就因為類太多引起爆炸了。小霍是個厲害的人物啊。
InputStream:是啊,年輕人就得多學習,小霍確實是個了不起的人物。
?
結束語:有人問過關于文章里提及的人物真實存在嗎?其實大多數都是我杜撰的。 而本文中的小霍確有其人。亞瑟.范.霍夫(Arthurvan Hoff)早期杰出的Java工程師,大多數的Java.io類都出自他手。后來也擔任過Flipboard,Dell公司CTO.謝謝他把這么精彩的設計帶到人間。文中提及的所有類InputFileStream,BufferedInputStream等都可以在java.io.*中找到,有興趣的可以去讀讀源碼,jdk的源碼就是最規范的java規范,最詳細的文檔。
總結
以上是生活随笔為你收集整理的对Java Inputstream的一次采访的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何实现一个Java Class解析器
- 下一篇: 微服务实战(二):使用API Gatew