通過上一節我們瞭解NIO的三大核心組件分別是Buffer、Channel、Selector。
三大核心部分的關聯圖:
- 每個channel都會對應一個Buffer
- Selector對應一個線程,一個線程對應多個channel
- 上圖顯示有三個channel註冊到selector上
- Selector是由事件驅動的,程序切換到哪個channel是由事件覺得的
- Selector會根據不同的時間,在各個通道上切換
- Buffer是一個內存塊,底層有一個數組
- 數據的讀取寫入是通過Buffer(BIO中要麼是輸入流、要麼是輸出流,不能雙向)。但是Buffer是可以讀也可以寫的,channel也是雙向的。
緩衝區Buffer
緩衝區(Buffer):本質上是一個可以讀寫數據的內存塊,可以理解成是一個容器對象(含數組),該對象提供了一組方法,可以輕鬆地使用內存塊,緩衝區對象內置了一些機制,能夠跟蹤和記錄緩衝區的狀態變化清情況。Channel提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據必須經有Buffer。
在NIO中,Buffer是一個頂層父類,它是一個抽象類,常用子類如下:
- ByteBuffer,存儲字節數據到緩衝區
- ShortBuffer,存儲字符串數據到緩衝區
- CharBuffer,存儲字符數據到緩衝區
- IntBuffer,存儲整數數據到緩衝區
- LongBuffer 存儲長整型數據到緩衝區
- DoubleBuffer 存儲小數到緩衝區
- FloatBuffer 存儲小數到緩衝區
- MappedByteBuffer 內存映射Buffer,可以實現數據在堆外內存中直接修改
Buffer類定義了所有的緩衝區都具有的四個屬性來提供關於其所包含的數據元素的信息:
屬性 | 描述 |
---|---|
capacity | 容量,即可以容納的做大數據量;在緩衝區創建時被設定並且不能改變 |
limit | 表示緩衝區的當前終點,不能對緩衝區超過極限的位置進行讀寫操作;且極限可以修改 |
position | 位置,下一個要被讀寫的元素的索引。每次讀寫緩衝區數據時都會改變該值,爲下次讀寫作準備 |
mark | 標記 |
能讀取的範圍是position-limit之間的元素,否則可能會讀取不到數據或者報錯
Buffer類相關方法:
public final int capacity()//返回此緩衝區的容量
public final int position()//返回此緩衝區的位置
public final Buffer position(int newPosition)//設置此緩衝區的位置
public final int limit()//返回此緩衝區的限制
public final Buffer limit(int newLimit)//設置此緩衝區的限制
public final Buffer mark()//在此緩衝區的位置設置標記
public final Buffer reset()//將此緩衝區的位置重置爲以前標記的位置
public final Buffer clear()//清除此緩衝區,將各個標記恢復到初始狀態,但是數據並沒有真正擦除;通常是在循環裏讀寫數據之前調用
public final Buffer flip() //反轉此緩衝區,通過是寫完要讀取的時候調用該方法
public final Buffer rewind() //重繞該緩衝區
public final int remaining()//返回當前位置與限制之間的元素數
public final boolean hasRemaining()//告知在當前位置和限制之間是否有元素
public abstract boolean isReadOnly()//告知此緩衝區是否爲只讀緩衝區
public abstract boolean hasArray()//告知此緩衝區是否具有可訪問的底層實現數組
public abstract Object array()//返回此緩衝區的底層實現數組
public abstract int arrayOffset()//返回此緩衝區的底層實現數組中第一個緩衝區元素的偏移量
public abstract boolean isDirect()//告知此緩衝區是否爲直接緩衝區
ByteBuffer
該類的主要方法如下:
public static ByteBuffer allocateDirect(int capacity);//創建直接緩衝區
public static ByteBuffer allocate(int capacity);//設置緩衝區的初始容量
public static ByteBuffer wrap(byte[] array);//把一個數組放到緩衝區中使用
public abstract byte get();//從當前位置position上get,get之後,position會自動+1
public abstract byte get();//從決定位置上get
public abstract ByteBuffer put(byte b);//從當前位置上添加,put之後,position會自動+1
public abstract ByteBuffer put(int index,byte b);//從絕對位置上put
例子
public class IntBufferDemo {
public static void main(String[] args) {
//實現類HeapIntBuffer
IntBuffer buffer = IntBuffer.allocate(5);
buffer.put(1);
buffer.put(2);
buffer.put(3);
buffer.put(4);
buffer.put(5);
//寫模式切換成讀模式 position/limit 數據變化
buffer.flip();
while (buffer.hasRemaining()) {
System.out.println(buffer.get());
}
}
}
ByteBuffer 支持類型化的 put 和 get, put 放入的是什麼數據類型,get 就應該使用相應的數據類型來取出,否則可能有 BufferUnderflowException異常,代碼如下:
public class ByteBufferDemo {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(40);
//這裏存放Double 需要8個字節 如果ByteBuffer空間小於8個字節 報錯BufferOverflowException
buffer.putDouble(0.1d);
buffer.put((byte) 127);
buffer.flip();
/*
ByteBuffer 支持類型化的 put 和 get, put 放入的是什麼數據類型,
get 就應該使用相應的數據類型來取出,否則可能有 BufferUnderflowException 異常
*/
System.out.println(buffer.getDouble());
System.out.println(buffer.get());
}
}
Buffer使用示例
Demo1:將一個普通Buffer轉成只讀Buffer
public class ReadBufferDemo {
public static void main(String[] args) {
//實際類是HeapByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
for (int i = 0; i < 16; i++) {
buffer.put((byte) i);
}
buffer.flip();
//只讀緩衝區會共享當前緩存區內容,即便當前buffer內容改變了 readOnlyBuffer也會同步改變 包括pos/limit等
//readOnlyBuffer 實現類實際是HeapByteBufferR
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
while (readOnlyBuffer.hasRemaining()) {
System.out.println(readOnlyBuffer.get());
}
//原始緩衝區發生變化則只讀緩衝區對應數據也變化了
buffer.put((byte) 16);
readOnlyBuffer.flip();
while (readOnlyBuffer.hasRemaining()) {
System.out.println(readOnlyBuffer.get());
}
//這裏會報錯 只讀緩衝區不能寫
readOnlyBuffer.put((byte) 17);
}
}