I/O的不少操做和使用,其實並非一個很是直觀的概念,特別是打開文件、建立buffer。這對於終端用戶來說是個很是奇葩和奇怪的過程。我只是想要從一個文件裏讀取內容,從過程上來說,我只須要知道:java
那我幹嗎要去關心神馬打開文件、建立stream和buffer?!編程
因此,要理解I/O這一套東西以及它所涉及的stream、buffer,你必須先理解計算機的底層是如何工做的。若是沒有這一步的底層基礎理論作支撐,全部的I/O操做將沒法變得直觀。數組
爲理解I/O所須要用到的底層知識並不算多,就幾點:緩存
面對以上這些現實,你不得不在programming時考慮上述問題。由於你不是終端用戶只須要一個簡單的接口。你是細節的操做者,必須對以上限制作出具體的可操做性的迴應。網絡
source --> buffer --> destination
,或者destination --> buffer --> source
。有了這部分的知識,咱們再來看「Java中的NIO是如何讀取文件的」就不會變得怪異了。架構
RandomAccessFile aFile = new RandomAccessFile("test.txt", "rw"); FileChannel inChannel = aFile.getChannel(); //create buffer with capacity of 48 bytes ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf); //read into buffer. while (bytesRead != -1) { buf.flip(); //make buffer ready for read while(buf.hasRemaining()){ System.out.print((char) buf.get()); // read 1 byte at a time } buf.clear(); //make buffer ready for writing bytesRead = inChannel.read(buf); } aFile.close();
讓咱們將以前討論的過程復現一遍:dom
RandomAccessFile
對象,來提供真實文件test.txt
在Java中對應的對象(能夠理解爲一個bean)。這個對象將提供各類服務來配合Java內部各類機制的操做。無疑,提供一個Channel
是它的本質工做之一。buffer
,而後將上面的文件Channel的字節流直接接入到這個buffer。System.out.print
對應的std io
。接下來能夠深刻更多的細節。編程語言
因爲buffer
是被重複利用的部分,因此這涉及到清理buffer的概念buf.clear()
。那你可能會說,爲何不能夠自動地清理buffer?由於這裏的buffer是一個底層的基礎服務。對於上層的應用來說,有些場景是須要清理buffer,有些場景是須要複用buffer的內容。你怎麼能夠一律而論地認爲全部應用場景都是隻須要消費一次buffer中的數據呢?因此,做爲底層設施來說,你必須提供足夠的靈活性,讓developer本身決定是否須要清理buffer。工具
再來比較奇怪的是flip()
這部分,爲何須要對buffer作flip操做呢?這就涉及到內存中buffer的管理問題。學習
這裏的管理方式其實很常見,在內存中,基本上都是以數組的形式提供堆棧結構來管理數據。那麼,這就涉及到對這個數組的操做問題。你要有一個表徵position的指針來指導數據的寫入方向。
若是多走一步,爲了驗證flip()
是否真的在讓position指針復原,你還可使用如下代碼:
System.out.println("Read " + bytesRead); // switch the buffer from writing mode into reading mode buf.flip(); while (buf.hasRemaining()) { System.out.println((char) buf.get()); } System.out.println("---------------------------"); // reset the pointer back to original point again buf.flip(); while (buf.hasRemaining()) { System.out.println((char) buf.get()); }
能夠看到,從buffer中又一次獲取到了一樣的信息。
從這個例子能夠看到不少深層次的東西。例如,爲何你必須瞭解底層的內存運做機制、操做系統的運起色制?由於這些細節決定了你該以什麼樣的方式去設計你的編程模式,也決定了你應該如何去理解編程語言中提供的一些機制,或者爲何一個庫應該會這樣設計。這是一切具體行動的現實。
「具有什麼功能」是這底層基礎設施提供的封裝好的API。但你要作的是programming的工做,不得不利用底層的基礎設施去構建新的服務和產品。若是沒辦法理解這些底層機制,你就沒辦法真正地去構建東西。不少的問題,其實能夠被繞過又或是不可能被實現,不在於邏輯有問題,而是單純的信息差,你並不知道這個構建出的抽閒概念下面隱藏的真實東西。
若是可以理解內存的利用方式,那麼,「分片、以stack的形式來作操做」的模式將成爲你本能的一部分。進而,涉及到的position移動或者flip()
的指針回調問題,就會成爲你的直觀。
固然,積累這部分的基礎知識是很是枯燥和乏味的。但如同全部的基本功,它們不會在短時間內爲你提供足夠的回報,但卻會爲你未來造成正確的「直覺」和「直觀」作出巨大的貢獻。
任何的抽象概念都具有直觀,只不過這個直觀所依賴的基礎不一樣。抽象如「機率論基礎」,其「形象」的直觀,實際上是數學系本科所學的「經典機率論」,不然你會迷失在「測度論」的細節裏。但這個「直觀」對於其它專業的人來說,並不直觀,甚至是異常複雜。而這個就是所謂的牢固的前提基礎知識。進一步,若是你想要學好「機率論基礎」這樣高度抽象的topic,你必須先夯實「經典機率論」這個基礎,必須先創建對它的深入認知。不然,你對「機率論基礎」的理解根本無從談起。
一樣的,若是你但願理解相似於編程語言中I/O庫的設計、理解各種緩存中間件、消息隊列中間件的設計,你必需要先創建「計算機如何運做」這個前提基礎。只有你熟悉了計算機的運做方式,可以以計算機底層的習慣去思考問題、處理問題,你纔可以看到各類組件設計的直觀,纔會看到各類莫名其妙的「繞路」究竟是在解決什麼、是爲了什麼。
因此,這樣一個學習過程是任何抽象技能所避免不了的。你必須經過反覆的閱讀和練習來掌握第一層的基礎概念,熟悉到讓這一層的抽象變成你腦海中的一個條件反射式的直觀。再這個新創建的直觀本能基礎上,你才能夠去理解更高層次的抽象。