Reactor模式稱爲叫反應堆或則反應器。在網絡編程 - BIO中,咱們瞭解到,大量的線程休眠致使資源浪費,在BIO中,經過Reactor來優化,下面舉個簡單的例子:
有個高級遊戲樂園,裏面有5個不一樣遊戲的玩法,玩家進來的時候,有一對一的工做人員給玩家講解每一個玩法。
傳統BIO模式是這樣的:
A玩家進來,工做人員1接待A,工做人員1講解完第一個遊戲玩法,而後A玩家開始玩,此時,工做人員1就在等待A玩家玩完(好比br.readLine())。玩家玩第二個遊戲的時候,工做人員1開始講解第二個遊戲玩法,而後A玩家繼續玩,一直玩到第五個遊戲。
若是還有玩家進來呢?那派出第二個工做人員,第三個工做人員。。。。。。
咱們能夠看到,工做人員在玩家玩的時候,他是處於休息空閒的狀態,並且玩家愈來愈多的時候,工做人員就須要愈來愈多。
Reactor模式:
工做人員1給玩家A講解完了,他就去處理其餘事情,而玩家開始玩遊戲,當玩家準備玩下一個遊戲的時候,他就呼叫工做人員,工做人員就過來說解下一個遊戲玩法。原本一個工做人員只能服務一個玩家,可是經過這個模式,他能夠同時服務多個玩家,在玩家A玩遊戲的時候,他能夠爲其餘玩家提供講解服務。
在Reactor模式中,應用程序並不會調用某個方法直至完成,而是逆置了事件處理流程,具體事件處理程序向反應器註冊一個事件處理器,等到事件來了,具體事件處理程序再處理相關事件。java
Buffer在NIO中,本質是一塊內存,用於和NIO通道進行交互。咱們能夠把數據從通道讀取中出來,寫入到Buffer,也能夠把Buffer的數據讀到出來,寫到通道中。
在NIO中,java定義了IntBuffer、FloatBuffer、ByteBuffer等,咱們比較經常使用的是ByteBuffer。
編程
Buffer有幾個重要的屬性:position、limit、capacity。segmentfault
下面經過一個簡單的例子深刻了解一下這幾個屬性。數組
public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(8); System.out.println("init:" + buffer); buffer.put((byte) 'a'); System.out.println("put-a:" + buffer); buffer.put((byte) 'b'); System.out.println("put-b:" + buffer); buffer.put((byte) 'c'); System.out.println("put-c:" + buffer); // 切換到讀模式 buffer.flip(); System.out.println("flip:" + buffer); buffer.get(); System.out.println("get-a:" + buffer); buffer.get(); System.out.println("get-b:" + buffer); buffer.get(); }
輸出結果以下:
初始化時,pos指向0,capacity和limit都等於指定大小8。
put-a時,pos+1,等於1,capacity和limit不變。
put-b時,pos+1,等於2,capacity和limit不變。
put-c時,pos+1,等於3,capacity和limit不變。
flip後,pos把值賦值給limit,並重置爲0,capacity不變。此時,pos等於0,limit等於3,capacity等於8。
get-a時,pos+1,等於1,capacity和limit不變。
get-b時,pos+1,等於1,capacity和limit不變。
網絡
allocate方法,在上面例子中,咱們看到了ByteBuffer.allocate(8)的使用。優化
public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); // lim也傳capacity,因此兩個剛開始是相等的 return new HeapByteBuffer(capacity, capacity); } HeapByteBuffer(int cap, int lim) { // 這邊pos賦值爲0,字節長度爲cap super(-1, 0, lim, cap, new byte[cap], 0); } ByteBuffer(int mark, int pos, int lim, int cap, byte[] hb, int offset) { super(mark, pos, lim, cap); this.hb = hb; this.offset = offset; }
wrap方法,跟allocate方法均可以初始化buffer,不一樣的是能夠指定pos和limit,以及指定字節數組的初始值。this
public static ByteBuffer wrap(byte[] array) { return wrap(array, 0, array.length); } public static ByteBuffer wrap(byte[] array, int offset, int length) { try { // 傳遞字節數組,pos,偏移量length,用於計算limit return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } }
除了上面例子演示的,put(byte),還有如下這些。
從源碼中看pos會加1的緣由:spa
public abstract ByteBuffer put(byte b); public ByteBuffer put(byte x) { hb[ix(nextPutIndex())] = x; return this; } final int nextPutIndex() { // package-private if (position >= limit) throw new BufferOverflowException(); // 這邊加1 return position++; }
也能夠把通道中的數據寫入到buffer:操作系統
// 這邊用read指的是把通道的數據讀取出來,再寫入buffer,read返回的是寫入buffer的數據大小。 channel.read(buf)
從源碼中也能夠看出,把pos的值賦值給limit,並重置爲0。線程
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
除了上面例子演示的,get,還有如下這些
從源碼中看pos會加1的緣由:
public abstract byte get(); public byte get() { return hb[ix(nextGetIndex())]; } final int nextGetIndex() { // package-private if (position >= limit) throw new BufferUnderflowException(); return position++; }
也能夠把buffer的數據寫入到通道中:
// 把buffer的數據讀取出來,寫入到channel中 channel.write(buf)
調用mark的時候,會把pos的值給mark,調用reset的時候,會把mark的值給pos。在實際過程當中,咱們在讀操做的時候,先調用mark方法標記位置,好比此時爲4,當咱們讀到7的時候,再調用reset方法,此時又從新從4開始讀。
public final Buffer mark() { mark = position; return this; } public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
從源碼能夠看出,rewind把pos置爲0,因此就是從頭開始讀寫。
clear方法,把pos置0,並重置limit爲capacity,這個時候進行寫的時候,就是從第一個位置開始寫,若是原先有數據,就是要被覆蓋,至關於清空了整個內存。
compact與clear不同的是,他會把pos和limit之間的數據,移到前面去,並設置pos的值,寫的時候,會重新的位置開始寫。好比pos爲2,limit爲4,他會把2-4之間的值移到0,再把pos設置爲2,這樣沒讀的數據,就不會被覆蓋而消失消失。
public final Buffer rewind() { position = 0; mark = -1; return this; } public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; } public abstract ByteBuffer compact(); public ByteBuffer compact() { System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); position(remaining()); limit(capacity()); discardMark(); return this; } public final int remaining() { return limit - position; }
Channel,通道,操做系統和應用程序之間的數據交互,就是經過通道來的。
選擇器,把Channel和須要的事件註冊到Selector上面,讓Selector進行監聽。這些事件包括如下幾種:
// 讀 public static final int OP_READ = 1 << 0; // 寫 public static final int OP_WRITE = 1 << 2; // 請求鏈接 public static final int OP_CONNECT = 1 << 3; // 接收鏈接 public static final int OP_ACCEPT = 1 << 4;
當須要監聽多個事件時,好比OP_ACCEPT和OP_CONNECT能夠這樣寫SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT
。