1. 程式人生 > >java原始碼之 io 流原始碼解讀(一)

java原始碼之 io 流原始碼解讀(一)

    剛剛喝了一波毒雞湯,其中印象最深的就是這兩個:

            沒有人能夠讓你放棄夢想,自己想想就放棄了。

            找物件的時候不能光看對方的外表。。。。  還要看看自己的外表    哈哈哈~~

    吸收了這一大波精氣之後,我感覺我的精力值已經足夠支撐我將這篇文章完成,嘿嘿   走你...\

    花了一週的零碎時間,整理了一下相關的類關係,雖然網上有很多,但是自己動手做一遍效果要好不知道多少。但是不知道怎麼傳大圖,只能將它調的很小穿上來。   嗯,上圖吧:

        

    咋一看,要比集合體系的圖複雜呢。。  哎,也正常,畢竟這要更高層一點吧,不在同一個位面嘿嘿嘿。 之前上課用的也好,自己下來練習的也好,總是將這些什麼流啊之類的搞混,整的自己暈頭轉向的,好不容易記住的大致的用法,過個十天半個月不用又是一臉懵逼了。。。。   於是狠下心來將其原始碼粗略看看,但是儘管這樣自己已經收穫到了很多。

    繼續說說流吧!    流,流量,水流,其英文單詞為Stream,我自然在idea上面去stream 檢視一下呢,果不其然,我找到了。經過幾分鐘的觀察驚訝的發現它竟然是java.util包下的。   哎呀媽呀這尷尬大了。   尋思還是跟著大牛的腳步走吧,免得自己又亂來嘿嘿嘿。  於是:以這張圖為索引:

        

    不得不說這下順了很多,但是我向來不喜歡被牽著鼻子走,遂還是從介面至下,慢慢專研,所謂心急吃不了熱豆腐(但是這速度已經很快了,強行吃熱豆腐肯定是要被燙嘴的嘻嘻)。   於是,通過原始碼很快的可以發現,io體系的頂層介面為  autoclosable,其方法介面很簡單,只有一個close()。   但是通過後面的除錯我大致能理解到,實現了autoclosable介面的類最後會交給 finilize管理,也就是jvm的垃圾收集器,至於它主要功能是什麼,只可意會不好言傳哈哈哈。  而往往類要呼叫的時候並不是直接實現autoclosable,而是實現其子介面closable,可能也是為了多型吧。。。    

    因此,從第一張關係圖中,可以看到:  io體系裡面所有的抽象基類:   InputStream,OutputStream,Reader,Writer都實現了該介面。   想表達的太多竟然一時間不知道該從哪個開始表述,那就按照自己觀看原始碼的時間線來說吧。

    以前就有所猜測,流的本質是什麼?  事實上並不存在流的類,有一個流的介面但是是作為java.util工具包下的介面,既然是作為一個介面,功能的抽象那麼問其本質就牛頭不對馬嘴了。   看來自己以後應該思考的要換成:  輸入流的本質是什麼?它的原理是什麼?  和  輸出流的本質是什麼?它的原理是什麼了吧。  儘管如此,原來猜測的答案便是:   陣列。  (這是作為一種邏輯結構而言,若從物理結構而言, 那所有的東西都可以稱之為陣列了吧,畢竟是線性的,當然這是在不考慮操作方式的情況下。不知道理解的對不對,管他的,有自己的理解我都是看成是經驗吧。)。  這一次的練習算是進一步加深了自己的理解吧,也算驗證了自己的猜測。  那就是 byte陣列。

    萬法歸宗,萬本歸源。  對於InputStream抽象基類而言,或者說成是輸入流而言,其最最最最最核心的莫過於 read()方法了,對於輸入類的基本流也好,包裝流也好,可以說它們都是在這上面做文章了,只是說呢,這文章做的逼格高了點,讓我等普通人不敢望其項背。  本尊面目如下:

        

    我第一個開刀的自然是用的最多,讓我痛苦不堪的  FileInputStram了。   不過整體而言它算中等複雜的一個類吧。(因為不復雜有點驕傲,那就中等複雜吧!)。 既然是叫做檔案輸入流,那肯定是跟檔案有關了,自然免不了要去看看File類。 之前朋友遇到過這樣一種情況,他希望在系統的檔案系統中建一個檔案,於是他採用了該方法。  new File("D:\\dev\\test.txt")。  執行後去檔案系統檢視的時候竟然沒有該檔案。  這也引起了我的思考,為什麼會沒有呢?  它們之間通過什麼聯絡在一起的呢?   沒想到一直拖到了現在才想起來去探究這個問題。  不過還好,不算太晚。      其滿足主要的一些依賴:

            

     可以看到FileInputStream依賴於兩個類,直接相關的是File,其又依賴於FileSystem,從名字也可以分析出這是跟檔案系統有關的抽象類,因為我是windows系統,所以它的實現類為  WinNTFileSystem,通過檢視原始碼,可以看到其中有多個native方法,也就是歸c管的,或者說歸系統管吧。

    既然類結構瞭解了,那就繼續來處理那個多年的疑問吧?  既然new File("...")不能建立檔案,那麼怎麼建立檔案呢?File類的邊界在哪?  它是以何種時機儲存我們所設定的資訊呢?   嗯差不多就這些問題,但是當時好像沒有截圖,就口頭表述吧。   事實上我們例項化一個檔案,也就是  new File()的時候,事實上並沒有與系統互動,其例項化後主要做了如下兩件事情:  1.規範化路徑名,也就是將分隔符換成本系統支援的分隔符;   2,判斷該路徑的型別,是完全絕對路徑呢?  還是相對專案根路徑的絕對路徑呢?  還是完全的相對路徑呢?  所以當我們例項化之後沒有檔案生成也就能理解了,那麼當我們呼叫例項化後的當前物件的createNewFile()之後,為什麼又有檔案呢? 它做了哪些事情呢?  請看圖:

        

        

        嘿嘿,是不是感覺有一種柳暗花明又一村的感覺哈哈哈。但是至少這裡我算粗魯的處理了我的疑問。

        上面那個已經超出了io流體系,算是一個延伸吧。。  繼續回過頭來,作為一個具體的輸入流,自然是要看它的最核心的方法的:

        

            至於其他的方法,多位元組讀取啊,按字元讀取啊,按字元陣列讀取啊,種種,原來最終其實都是執行的這個,真扯淡,原來以為挺神祕的。

            關於多位元組讀取需要說明的一點就是:  多位元組讀取為過程呼叫,使用方式上面,跟單位元組讀取有一些差別。單位元組讀取為函式呼叫,其結果通過返回值呈現。  而多位元組讀取為過程呼叫,其返回值雖然也為int但是意義變了,目標引數為複合資料型別 陣列,因此結果的呈現直接去我們定義的目標byte[]中取。這也是我長久來的一個疑惑吧。  但是本質上,都是read()負責讀取,並且是單位元組讀取。方法如下:

            

            還有一個挺重要的就是,FileInputStream何種時機與系統互動?

        

        

        嘿嘿,它在例項化輸入流的時候就互動了,就是這麼暴力。  當然前提是你以檔案去例項化。

        對啦對啦,還有一個特別重要的話題!!  那就是既然read0()方法是外部的,並且是一個函式呼叫,也就是每次只需讀取需要的那個byte,而對讀取狀態,讀取位置的推進,讀取安全性等都不是java去維護,那麼誰對這個執行緒負責,誰來管理維護呢?  總的有人管理吧,不然早亂了套了。    具體是誰我也不是很清楚,但是我猜想有幾種可能性:   1.協議棧去維護,比如tcp協議,它就是一個基於連線的可靠的傳輸機制,其通訊過程中本身就記錄了狀態資訊,位置資訊等。    2.c函式去維護,即通過jni呼叫的c函式,其本質也是一個高階程式管理的。(最近看的一些書,讓我對所謂的高階語言,低階語言有了一些特別的認識。  比如asm(組合語言)與c(c語言),可以說是作為高階與低階語言的典型代表了。  但是其實asm裡面也是那些跳轉語句,移位語句啊之類的,只是它在表達方式讓對人來說不易理解,並且寫起來很麻煩。而c則更易讀,實際上(自己的理解不知道對否)是對asm的一個包裝。這就讓我對軟硬體更加的著迷了,奈何自己要搞錢,不然肯定去深深專研去了。)。  不管怎麼樣吧,反正跟java 內部無關,有一句話是這樣說的,程式在沒有被呼叫的時候在硬碟中,被呼叫的時候就從記憶體中複製到記憶體中執行,也就是執行的程式都是在記憶體中的。  從這個意義上講,這個FileInputStream是從外部讀取東西。   到這裡,基本算是我對這個輸入流的所有的疑惑與感悟了。

    萬事開頭難。  既然處理了一個輸入流了,那麼接下來的應該都好辦了。    第二個是最複雜的一個,類最多的一個,但同時它也是最懶的。   為啥呢??  從它的名字可以看出來:   FilterInputStream,顧名思義,過濾輸入流,哈哈哈翻譯的很生硬。但是它確實類比較多,也很複雜。  你看:  BufferedInputStream,DataInputStream,還有一個跟這個有一點血緣關係的  最複雜的 ObjectInputStram(似曾相識啊,要麼在觀看大牛解析tomcat的時候好像看到過它,要不就是在debug springboot的時候看到過它。 總之逼格有點高)。   為什麼說它們是最懶的呢?  因為它們沒有自己去讀取啊 哈哈哈。  事實上,它們最後都呼叫的是他們的父類InputStream去實現read()方法,但是總的有人去read()啊。  “額,我才不管呢,要麼你就乖乖的讀取給我,我給你封裝一下,要麼你就別用我們。"它們如是說。

    在這個過程中,我算好好理解了一下buffer這個東西了(說法可能不嚴謹,因為buffer事實上是一個抽象基類,而且還不是屬於io包下的)。但是它們都有一些基本的特徵:   那就是既然是緩衝,其自然是為了減少峰值的。   所以它們必須都有一個東西來裝,類似於半山腰的水池。  那麼這個水池實際上是個什麼玩意呢?    萬法歸宗,萬本歸源, 自然是byte[]。  嘿嘿嘿,而且是位於記憶體中,也就是java內部。    我還發現一個不成文的規定,那就是它們有一個預設大小,就是 2的十三次方,也就是8192.。   還有我們平常見過特別多的,flush()方法。   但是BufferInputStream裡面好像不叫flush(),叫做fill()。  那就等到了包裝流的時候再繼續介紹吧。 (其本質也是呼叫read()核心方法嘿嘿。)

          不知不覺都到了寢室要關門的時候了,正寫的起勁呢。哎,連InputStream都沒有寫完太失望了。  回寢室繼續寫吧,爭取忙完。