好吧如果你理解了上面的東西,並且真正的補全了文件備份的小程序,那麼就來進行稍微深入一點的學習吧。
上文我提到了舉吸煙的例子是有欠妥當的,其中之一就是Buffer的內部機制和"水斗"簡單的過濾功能是不一樣的。還有字符編碼那一塊也不是在Buffer內部實現的東西,decoder和encoder是針對Buffer的兩個工具。那我們接下來分析一下Buffer內部機制到底不一樣在哪裡呢(主要分析常用的兩個方法;clear(),flip())?
來吧,打開Buffer的源碼(摘取有用的部分):
- public abstract class Buffer {
- // Invariants: mark <= position <= limit <= capacity
- private int mark = -1;
- private int position = 0;
- private int limit;
- private int capacity;
- public final Buffer clear() {
- position = 0;
- limit = capacity;
- mark = -1;
- return this;
- }
- public final Buffer flip() {
- limit = position;
- position = 0;
- mark = -1;
- return this;
- }
首先我們要明確一點,所謂的緩沖器僅僅是一個"多功能"的數組。可能在這個Buffer類中沒有體現,但是如果我們打開ByteBuffer的源碼會有byte[]的數組,打開CharBuffer的源碼會有char[]的數組。因為Buffer是所有緩沖器的父類,所以他它不能預計會有多少種緩沖器,所以索性讓"兒子"們自己實現去吧。
既然知道了緩沖器是一個"多功能的數組",那麼我們用畫圖的形式來分析一下上面Buffer的源碼。
假設我們定義了一個8個單位大的緩沖區,如上圖(其實Buffer也就是這麼一個東西)。首先告訴大家那三個重要的關於緩沖區狀態的的屬性:
capacity:緩沖區的容量;
limit:緩沖區還有多少數據能夠取出或者緩沖區還有多少容量用於存放數據;
position:相當於一個游標(cursor),記錄我們從哪裡開始寫數據,從哪裡開始讀數據。
剛還說到flip()和clear()是Buffer的兩個重要的方法,因為它們兩個方法決定了緩沖是否能正常的進行讀寫工作。
當我們要想從緩沖區中寫數據的時候必須先執行flip()方法,當我們要想從緩沖區中讀數據時必須先執行clear()方法。
第一次向Buffer中寫入數據時,執行一次flip()方法以後,Buffer的結構變成了這樣:position指向了第一個可以存取數據的0號位,limit和capacity同時指向最高位。
假如第一次我們向Buffer中寫入了3單位的數據,我們再次執行flip()方法則Buffer的結構會變成上圖的所示。但是經過flip()的改造後position總是指向Buffer中第一個可用的位置。那麼,未執行flip()方法以前position在哪裡呢?很簡單,指向最後一個數據的位置。
當我們想要從Buffer中讀取數據時,執行clear()方法,Buffer的內部結構變成了上圖所示,position指向了可讀數據的首位,limit指向了原來position的位置。
從上面的幾幅圖中我們看出:capacity代表了Buffer的容量是不變的,limit與position的差總是表示Buffer總可以讀的數據,或者Buffer中可以寫數據的容量。還有position總是小於等於limit,limit總是小於等於capacity。
其實到這裡我們已經發現,NIO並不像IO那麼復雜,因為IO 中的Decorator模式和Adaptor模式確實讓我們一時間摸不到頭腦,但是熟悉了會感覺到IO的設計之精美。
NIO中還有一個知識點就是無阻塞的Socket編程,這裡就不說了,因為比較復雜,但是如果我們真正理解了Selector這個調度者的工作,那麼無阻塞的實現機制我們差不多就掌握了,復雜也就是編碼上面的事了。