ByteBuffer使用之道
生活随笔
收集整理的這篇文章主要介紹了
ByteBuffer使用之道
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
?緩沖區分配和包裝?
在能夠讀和寫之前,必須有一個緩沖區,用靜態方法 allocate() 來分配緩沖區:?
ByteBuffer buffer = ByteBuffer.allocate(1024);?
allocate() 方法分配一個具有指定大小的底層數組,并將它包裝到一個緩沖區對象中 — 在本例中是一個 ByteBuffer。?
還可以將一個現有的數組轉換為緩沖區:?
byte array[] = new byte[1024];?
ByteBuffer buffer = ByteBuffer.wrap(array);?
本例使用了 wrap() 方法將一個數組包裝為緩沖區。一旦完成包裝,底層數據就可以通過緩沖區或者直接訪問。?
緩沖區分片?
slice() 方法根據現有的緩沖區創建一個 子緩沖區。也就是它創建一個新的緩沖區,新緩沖區與原來的緩沖區的一部分共享數據。?
首先創建一個長度為 10 的 ByteBuffer:?
ByteBuffer buffer = ByteBuffer.allocate(10);?
然后使用數據來填充這個緩沖區,在第 n 個槽中放入數字 n:?
for (int i = 0; i < buffer.capacity(); i++) {?
???? buffer.put((byte)i);?
}?
現在我們對這個緩沖區 分片,以創建一個包含槽 3 到槽 6 的子緩沖區。在某種意義上,子緩沖區就像原來的緩沖區中的一個 窗口 。?
窗口的起始和結束位置通過設置 position 和 limit 值來指定,然后調用 Buffer 的 slice() 方法:?
buffer.position(3);?
buffer.limit(7);?
ByteBuffer slice = buffer.slice();?
片 是緩沖區的 子緩沖區 。不過,片段 和 緩沖區 共享同一個底層數據數組。?
緩沖區份片和數據共享?
遍歷子緩沖區,將每一個元素乘以 11 來改變它。例如,5 會變成 55。?
for (int i = 0; i < slice.capacity(); i++) {?
???? byte b = slice.get(i);?
???? b *= 11;?
???? slice.put(i, b);?
}?
原緩沖區中的內容:?
buffer.position( 0 );?
buffer.limit(buffer.capacity());?
while (buffer.hasRemaining()) {?
???? System.out.println(buffer.get());?
}?
結果表明只有在子緩沖區窗口中的元素被改變了:?
java SliceBuffer?
0?
1?
2?
33?
44?
55?
66?
7?
8?
9?
緩沖區片對于促進抽象非常有幫助??梢跃帉懽约旱暮瘮堤幚碚麄€緩沖區,而且如果想要將這個過程應用于子緩沖區上,只需取主緩沖區的一個片,并將它傳遞給您的函數。這比編寫自己的函數來取額外的參數以指定要對緩沖區的哪一部分進行操作更容易。?
只讀緩沖區?
只讀緩沖區非常簡單 — 可以讀取它們,但是不能向它們寫入。可以通過調用緩沖區的 asReadOnlyBuffer() 方法,將任何常規緩沖區轉換為只讀緩沖區,這個方法返回一個與原緩沖區完全相同的緩沖區(并與其共享數據),只不過它是只讀的。?
只讀緩沖區對于保護數據很有用。在將緩沖區傳遞給某個對象的方法時,您無法知道這個方法是否會修改緩沖區中的數據。創建一個只讀的緩沖區可以 保證 該緩沖區不會被修改。?
不能將只讀的緩沖區轉換為可寫的緩沖區。?
內存映射文件 I/O?
內存映射文件 I/O 是一種讀和寫文件數據的方法,它可以比常規的基于流或者基于通道的 I/O 快得多。?
內存映射文件 I/O 是通過使文件中的數據神奇般地出現為內存數組的內容來完成的。這其初聽起來似乎不過就是將整個文件讀到內存中,但是事實上并不是這樣。一般來說,只有文件中實際讀取或者寫入的部分才會送入(或者 映射 )到內存中。?
內存映射并不真的神奇或者多么不尋?!,F代操作系統一般根據需要將文件的部分映射為內存的部分,從而實現文件系統。Java 內存映射機制不過是在底層操作系統中可以采用這種機制時,提供了對該機制的訪問。?
盡管創建內存映射文件相當簡單,但是向它寫入可能是危險的。僅只是改變數組的單個元素這樣的簡單操作,就可能會直接修改磁盤上的文件。修改數據與將數據保存到磁盤是沒有分開的。?
將文件映射到內存?
了解內存映射的最好方法是使用例子。在下面的例子中,要將一個 FileChannel (它的全部或者部分)映射到內存中。為此將使用 FileChannel.map() 方法。下面代碼行將文件的前 1024 個字節映射到內存中:?
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024 );?
map() 方法返回一個 MappedByteBuffer,它是 ByteBuffer 的子類。因此,可以像使用其他任何 ByteBuffer 一樣使用新映射的緩沖區,操作系統會在需要時負責執行行映射。?
import java.io.RandomAccessFile;?
import java.nio.MappedByteBuffer;?
import java.nio.channels.FileChannel;?
public class UseMappedFile {?
??? private static final int start = 0;?
??? private static final int size = 1024;?
??? public static void main(String args[]) throws Exception {?
??????? RandomAccessFile raf = new RandomAccessFile("C:\\usemappedfile.txt", "rw");?
??????? FileChannel fc = raf.getChannel();?
??????? MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size);?
??????? mbb.put(0, (byte)97);?
??????? mbb.put(1023, (byte)122);?
??????? raf.close();?
??? }?
}?
分散和聚集?
分散/聚集 I/O 是使用多個而不是單個緩沖區來保存數據的讀寫方法。?
一個分散的讀取就像一個常規通道讀取,只不過它是將數據讀到一個緩沖區數組中而不是讀到單個緩沖區中。同樣地,一個聚集寫入是向緩沖區數組而不是向單個緩沖區寫入數據。?
分散/聚集 I/O 對于將數據流劃分為單獨的部分很有用,這有助于實現復雜的數據格式。?
分散/聚集 I/O?
通道可以有選擇地實現兩個新的接口: ScatteringByteChannel 和 GatheringByteChannel。一個 ScatteringByteChannel 是一個具有兩個附加讀方法的通道:?
long read(ByteBuffer[] dsts);?
long read(ByteBuffer[] dsts, int offset, int length);?
這些 long read() 方法很像標準的 read 方法,只不過它們不是取單個緩沖區而是取一個緩沖區數組。?
在 分散讀取 中,通道依次填充每個緩沖區。填滿一個緩沖區后,它就開始填充下一個。在某種意義上,緩沖區數組就像一個大緩沖區。?
分散/聚集 I/O 對于將數據劃分為幾個部分很有用。例如,您可能在編寫一個使用消息對象的網絡應用程序,每一個消息被劃分為固定長度的頭部和固定長度的正文。您可以創建一個剛好可以容納頭部的緩沖區和另一個剛好可以容難正文的緩沖區。當您將它們放入一個數組中并使用分散讀取來向它們讀入消息時,頭部和正文將整齊地劃分到這兩個緩沖區中。?
從緩沖區所得到的方便性對于緩沖區數組同樣有效。因為每一個緩沖區都跟蹤自己還可以接受多少數據,所以分散讀取會自動找到有空間接受數據的第一個緩沖區。在這個緩沖區填滿后,它就會移動到下一個緩沖區。?
聚集寫入?
聚集寫入 類似于分散讀取,只不過是用來寫入。它也有接受緩沖區數組的方法:?
long write(ByteBuffer[] srcs);?
long write(ByteBuffer[] srcs, int offset, int length);?
聚集寫對于把一組單獨的緩沖區中組成單個數據流很有用。為了與上面的消息例子保持一致,您可以使用聚集寫入來自動將網絡消息的各個部分組裝為單個數據流,以便跨越網絡傳輸消息。?
異步 I/O?
異步 I/O 是一種 沒有阻塞地 讀寫數據的方法。通常,在代碼進行 read() 調用時,代碼會阻塞直至有可供讀取的數據。同樣, write() 調用將會阻塞直至數據能夠寫入。?
另一方面,異步 I/O 調用不會阻塞。相反,您將注冊對特定 I/O 事件的興趣 — 可讀的數據的到達、新的套接字連接,等等,而在發生這樣的事件時,系統將會告訴您。?
異步 I/O 的一個優勢在于,它允許您同時根據大量的輸入和輸出執行 I/O。同步程序常常要求助于輪詢,或者創建許許多多的線程以處理大量的連接。使用異步 I/O,您可以監聽任何數量的通道上的事件,不用輪詢,也不用額外的線程。?
異步 I/O 中的核心對象名為 Selector。Selector 就是您注冊對各種 I/O 事件的興趣的地方,而且當那些事件發生時,就是這個對象告訴您所發生的事件。?
創建一個 Selector?
Selector selector = Selector.open();?
然后,將對不同的通道對象調用 register() 方法,以便注冊我們對這些對象中發生的 I/O 事件的興趣。register() 的第一個參數總是這個 Selector。?
打開一個 ServerSocketChannel?
為了接收連接,需要一個 ServerSocketChannel。事實上,要監聽的每一個端口都需要有一個 ServerSocketChannel 。對于每一個端口,打開一個 ServerSocketChannel,如下所示:?
ServerSocketChannel ssc = ServerSocketChannel.open();?
ssc.configureBlocking( false );?
ServerSocket ss = ssc.socket();?
InetSocketAddress address = new InetSocketAddress(ports[i]);?
ss.bind( address );?
第一行創建一個新的 ServerSocketChannel,最后三行將它綁定到給定的端口。第二行將 ServerSocketChannel 設置為 非阻塞的 。必須對每一個要使用的套接字通道調用這個方法,否則異步 I/O 就不能工作。?
選擇鍵?
下一步是將新打開的 ServerSocketChannels 注冊到 Selector上。為此使用 ServerSocketChannel.register() 方法,如下所示:?
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);?
register() 的第一個參數總是這個 Selector。第二個參數是 OP_ACCEPT,這里它指定我們想要監聽 accept 事件,也就是在新的連接建立時所發生的事件。這是適用于 ServerSocketChannel 的唯一事件類型。?
請注意對 register() 的調用的返回值。 SelectionKey 代表這個通道在此 Selector 上的這個注冊。當某個 Selector 通知您某個傳入事件時,它是通過提供對應于該事件的 SelectionKey 來進行的。SelectionKey 還可以用于取消通道的注冊。?
內部循環?
現在已經注冊了對一些 I/O 事件的興趣,下面將進入主循環。使用 Selectors 的幾乎每個程序都像下面這樣使用內部循環:?
int num = selector.select();?
Set selectedKeys = selector.selectedKeys();?
Iterator it = selectedKeys.iterator();?
while (it.hasNext()) {?
???? SelectionKey key = (SelectionKey)it.next();?
???? // ... deal with I/O event ...?
}?
首先,調用 Selector 的 select() 方法。這個方法會阻塞,直到至少有一個已注冊的事件發生。當一個或者更多的事件發生時, select() 方法將返回所發生的事件的數量。?
接下來,調用 Selector 的 selectedKeys() 方法,它返回發生了事件的 SelectionKey 對象的一個 集合 。?
通過迭代 SelectionKeys 并依次處理每個 SelectionKey 來處理事件。對于每一個 SelectionKey,必須確定發生的是什么 I/O 事件,以及這個事件影響哪些 I/O 對象。?
監聽新連接?
程序執行到這里,僅注冊了 ServerSocketChannel,并且僅注冊它們“接收”事件。為確認這一點,對 SelectionKey 調用 readyOps() 方法,并檢查發生了什么類型的事件:?
if(key.isAcceptable()) {?
???? // Accept the new connection?
}?
可以肯定地說, readOps() 方法告訴我們該事件是新的連接。?
接受新的連接?
因為我們知道這個服務器套接字上有一個傳入連接在等待,所以可以安全地接受它;也就是說,不用擔心 accept() 操作會阻塞:?
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();?
SocketChannel sc = ssc.accept();?
下一步是將新連接的 SocketChannel 配置為非阻塞的。而且由于接受這個連接的目的是為了讀取來自套接字的數據,所以我們還必須將 SocketChannel 注冊到 Selector上,如下所示:?
sc.configureBlocking(false);?
SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);?
注意使用 register() 的 OP_READ 參數,將 SocketChannel 注冊用于 讀取 而不是 接受 新連接。?
刪除處理過的 SelectionKey?
在處理 SelectionKey 之后,我們幾乎可以返回主循環了。但是我們必須首先將處理過的 SelectionKey 從選定的鍵集合中刪除。如果我們沒有刪除處理過的鍵,那么它仍然會在主集合中以一個激活的鍵出現,這會導致我們嘗試再次處理它。我們調用迭代器的 remove() 方法來刪除處理過的 SelectionKey:?
it.remove();?
現在我們可以返回主循環并接受從一個套接字中傳入的數據(或者一個傳入的 I/O 事件)了。?
傳入的 I/O?
當來自一個套接字的數據到達時,它會觸發一個 I/O 事件。這會導致在主循環中調用 Selector.select(),并返回一個或者多個 I/O 事件。這一次, SelectionKey 將被標記為 OP_READ 事件,如下所示:?
} else if (key.isReadable()) {?
???? // Read the data?
???? SocketChannel sc = (SocketChannel)key.channel();?
}?
與以前一樣,我們取得發生 I/O 事件的通道并處理它。?
回到主循環?
每次返回主循環,都要調用 select 的 Selector()方法,并取得一組 SelectionKey。每個鍵代表一個 I/O 事件。我們處理事件,從選定的鍵集中刪除 SelectionKey,然后返回主循環的頂部。?
這個程序有點過于簡單,因為它的目的只是展示異步 I/O 所涉及的技術。在現實的應用程序中,您需要通過將通道從 Selector 中刪除來處理關閉的通道。而且您可能要使用多個線程。這個程序可以僅使用一個線程,因為它只是一個演示,但是在現實場景中,創建一個線程池來負責 I/O 事件處理中的耗時部分會更有意義。?
在能夠讀和寫之前,必須有一個緩沖區,用靜態方法 allocate() 來分配緩沖區:?
ByteBuffer buffer = ByteBuffer.allocate(1024);?
allocate() 方法分配一個具有指定大小的底層數組,并將它包裝到一個緩沖區對象中 — 在本例中是一個 ByteBuffer。?
還可以將一個現有的數組轉換為緩沖區:?
byte array[] = new byte[1024];?
ByteBuffer buffer = ByteBuffer.wrap(array);?
本例使用了 wrap() 方法將一個數組包裝為緩沖區。一旦完成包裝,底層數據就可以通過緩沖區或者直接訪問。?
緩沖區分片?
slice() 方法根據現有的緩沖區創建一個 子緩沖區。也就是它創建一個新的緩沖區,新緩沖區與原來的緩沖區的一部分共享數據。?
首先創建一個長度為 10 的 ByteBuffer:?
ByteBuffer buffer = ByteBuffer.allocate(10);?
然后使用數據來填充這個緩沖區,在第 n 個槽中放入數字 n:?
for (int i = 0; i < buffer.capacity(); i++) {?
???? buffer.put((byte)i);?
}?
現在我們對這個緩沖區 分片,以創建一個包含槽 3 到槽 6 的子緩沖區。在某種意義上,子緩沖區就像原來的緩沖區中的一個 窗口 。?
窗口的起始和結束位置通過設置 position 和 limit 值來指定,然后調用 Buffer 的 slice() 方法:?
buffer.position(3);?
buffer.limit(7);?
ByteBuffer slice = buffer.slice();?
片 是緩沖區的 子緩沖區 。不過,片段 和 緩沖區 共享同一個底層數據數組。?
緩沖區份片和數據共享?
遍歷子緩沖區,將每一個元素乘以 11 來改變它。例如,5 會變成 55。?
for (int i = 0; i < slice.capacity(); i++) {?
???? byte b = slice.get(i);?
???? b *= 11;?
???? slice.put(i, b);?
}?
原緩沖區中的內容:?
buffer.position( 0 );?
buffer.limit(buffer.capacity());?
while (buffer.hasRemaining()) {?
???? System.out.println(buffer.get());?
}?
結果表明只有在子緩沖區窗口中的元素被改變了:?
java SliceBuffer?
0?
1?
2?
33?
44?
55?
66?
7?
8?
9?
緩沖區片對于促進抽象非常有幫助??梢跃帉懽约旱暮瘮堤幚碚麄€緩沖區,而且如果想要將這個過程應用于子緩沖區上,只需取主緩沖區的一個片,并將它傳遞給您的函數。這比編寫自己的函數來取額外的參數以指定要對緩沖區的哪一部分進行操作更容易。?
只讀緩沖區?
只讀緩沖區非常簡單 — 可以讀取它們,但是不能向它們寫入。可以通過調用緩沖區的 asReadOnlyBuffer() 方法,將任何常規緩沖區轉換為只讀緩沖區,這個方法返回一個與原緩沖區完全相同的緩沖區(并與其共享數據),只不過它是只讀的。?
只讀緩沖區對于保護數據很有用。在將緩沖區傳遞給某個對象的方法時,您無法知道這個方法是否會修改緩沖區中的數據。創建一個只讀的緩沖區可以 保證 該緩沖區不會被修改。?
不能將只讀的緩沖區轉換為可寫的緩沖區。?
內存映射文件 I/O?
內存映射文件 I/O 是一種讀和寫文件數據的方法,它可以比常規的基于流或者基于通道的 I/O 快得多。?
內存映射文件 I/O 是通過使文件中的數據神奇般地出現為內存數組的內容來完成的。這其初聽起來似乎不過就是將整個文件讀到內存中,但是事實上并不是這樣。一般來說,只有文件中實際讀取或者寫入的部分才會送入(或者 映射 )到內存中。?
內存映射并不真的神奇或者多么不尋?!,F代操作系統一般根據需要將文件的部分映射為內存的部分,從而實現文件系統。Java 內存映射機制不過是在底層操作系統中可以采用這種機制時,提供了對該機制的訪問。?
盡管創建內存映射文件相當簡單,但是向它寫入可能是危險的。僅只是改變數組的單個元素這樣的簡單操作,就可能會直接修改磁盤上的文件。修改數據與將數據保存到磁盤是沒有分開的。?
將文件映射到內存?
了解內存映射的最好方法是使用例子。在下面的例子中,要將一個 FileChannel (它的全部或者部分)映射到內存中。為此將使用 FileChannel.map() 方法。下面代碼行將文件的前 1024 個字節映射到內存中:?
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024 );?
map() 方法返回一個 MappedByteBuffer,它是 ByteBuffer 的子類。因此,可以像使用其他任何 ByteBuffer 一樣使用新映射的緩沖區,操作系統會在需要時負責執行行映射。?
import java.io.RandomAccessFile;?
import java.nio.MappedByteBuffer;?
import java.nio.channels.FileChannel;?
public class UseMappedFile {?
??? private static final int start = 0;?
??? private static final int size = 1024;?
??? public static void main(String args[]) throws Exception {?
??????? RandomAccessFile raf = new RandomAccessFile("C:\\usemappedfile.txt", "rw");?
??????? FileChannel fc = raf.getChannel();?
??????? MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size);?
??????? mbb.put(0, (byte)97);?
??????? mbb.put(1023, (byte)122);?
??????? raf.close();?
??? }?
}?
分散和聚集?
分散/聚集 I/O 是使用多個而不是單個緩沖區來保存數據的讀寫方法。?
一個分散的讀取就像一個常規通道讀取,只不過它是將數據讀到一個緩沖區數組中而不是讀到單個緩沖區中。同樣地,一個聚集寫入是向緩沖區數組而不是向單個緩沖區寫入數據。?
分散/聚集 I/O 對于將數據流劃分為單獨的部分很有用,這有助于實現復雜的數據格式。?
分散/聚集 I/O?
通道可以有選擇地實現兩個新的接口: ScatteringByteChannel 和 GatheringByteChannel。一個 ScatteringByteChannel 是一個具有兩個附加讀方法的通道:?
long read(ByteBuffer[] dsts);?
long read(ByteBuffer[] dsts, int offset, int length);?
這些 long read() 方法很像標準的 read 方法,只不過它們不是取單個緩沖區而是取一個緩沖區數組。?
在 分散讀取 中,通道依次填充每個緩沖區。填滿一個緩沖區后,它就開始填充下一個。在某種意義上,緩沖區數組就像一個大緩沖區。?
分散/聚集 I/O 對于將數據劃分為幾個部分很有用。例如,您可能在編寫一個使用消息對象的網絡應用程序,每一個消息被劃分為固定長度的頭部和固定長度的正文。您可以創建一個剛好可以容納頭部的緩沖區和另一個剛好可以容難正文的緩沖區。當您將它們放入一個數組中并使用分散讀取來向它們讀入消息時,頭部和正文將整齊地劃分到這兩個緩沖區中。?
從緩沖區所得到的方便性對于緩沖區數組同樣有效。因為每一個緩沖區都跟蹤自己還可以接受多少數據,所以分散讀取會自動找到有空間接受數據的第一個緩沖區。在這個緩沖區填滿后,它就會移動到下一個緩沖區。?
聚集寫入?
聚集寫入 類似于分散讀取,只不過是用來寫入。它也有接受緩沖區數組的方法:?
long write(ByteBuffer[] srcs);?
long write(ByteBuffer[] srcs, int offset, int length);?
聚集寫對于把一組單獨的緩沖區中組成單個數據流很有用。為了與上面的消息例子保持一致,您可以使用聚集寫入來自動將網絡消息的各個部分組裝為單個數據流,以便跨越網絡傳輸消息。?
異步 I/O?
異步 I/O 是一種 沒有阻塞地 讀寫數據的方法。通常,在代碼進行 read() 調用時,代碼會阻塞直至有可供讀取的數據。同樣, write() 調用將會阻塞直至數據能夠寫入。?
另一方面,異步 I/O 調用不會阻塞。相反,您將注冊對特定 I/O 事件的興趣 — 可讀的數據的到達、新的套接字連接,等等,而在發生這樣的事件時,系統將會告訴您。?
異步 I/O 的一個優勢在于,它允許您同時根據大量的輸入和輸出執行 I/O。同步程序常常要求助于輪詢,或者創建許許多多的線程以處理大量的連接。使用異步 I/O,您可以監聽任何數量的通道上的事件,不用輪詢,也不用額外的線程。?
異步 I/O 中的核心對象名為 Selector。Selector 就是您注冊對各種 I/O 事件的興趣的地方,而且當那些事件發生時,就是這個對象告訴您所發生的事件。?
創建一個 Selector?
Selector selector = Selector.open();?
然后,將對不同的通道對象調用 register() 方法,以便注冊我們對這些對象中發生的 I/O 事件的興趣。register() 的第一個參數總是這個 Selector。?
打開一個 ServerSocketChannel?
為了接收連接,需要一個 ServerSocketChannel。事實上,要監聽的每一個端口都需要有一個 ServerSocketChannel 。對于每一個端口,打開一個 ServerSocketChannel,如下所示:?
ServerSocketChannel ssc = ServerSocketChannel.open();?
ssc.configureBlocking( false );?
ServerSocket ss = ssc.socket();?
InetSocketAddress address = new InetSocketAddress(ports[i]);?
ss.bind( address );?
第一行創建一個新的 ServerSocketChannel,最后三行將它綁定到給定的端口。第二行將 ServerSocketChannel 設置為 非阻塞的 。必須對每一個要使用的套接字通道調用這個方法,否則異步 I/O 就不能工作。?
選擇鍵?
下一步是將新打開的 ServerSocketChannels 注冊到 Selector上。為此使用 ServerSocketChannel.register() 方法,如下所示:?
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);?
register() 的第一個參數總是這個 Selector。第二個參數是 OP_ACCEPT,這里它指定我們想要監聽 accept 事件,也就是在新的連接建立時所發生的事件。這是適用于 ServerSocketChannel 的唯一事件類型。?
請注意對 register() 的調用的返回值。 SelectionKey 代表這個通道在此 Selector 上的這個注冊。當某個 Selector 通知您某個傳入事件時,它是通過提供對應于該事件的 SelectionKey 來進行的。SelectionKey 還可以用于取消通道的注冊。?
內部循環?
現在已經注冊了對一些 I/O 事件的興趣,下面將進入主循環。使用 Selectors 的幾乎每個程序都像下面這樣使用內部循環:?
int num = selector.select();?
Set selectedKeys = selector.selectedKeys();?
Iterator it = selectedKeys.iterator();?
while (it.hasNext()) {?
???? SelectionKey key = (SelectionKey)it.next();?
???? // ... deal with I/O event ...?
}?
首先,調用 Selector 的 select() 方法。這個方法會阻塞,直到至少有一個已注冊的事件發生。當一個或者更多的事件發生時, select() 方法將返回所發生的事件的數量。?
接下來,調用 Selector 的 selectedKeys() 方法,它返回發生了事件的 SelectionKey 對象的一個 集合 。?
通過迭代 SelectionKeys 并依次處理每個 SelectionKey 來處理事件。對于每一個 SelectionKey,必須確定發生的是什么 I/O 事件,以及這個事件影響哪些 I/O 對象。?
監聽新連接?
程序執行到這里,僅注冊了 ServerSocketChannel,并且僅注冊它們“接收”事件。為確認這一點,對 SelectionKey 調用 readyOps() 方法,并檢查發生了什么類型的事件:?
if(key.isAcceptable()) {?
???? // Accept the new connection?
}?
可以肯定地說, readOps() 方法告訴我們該事件是新的連接。?
接受新的連接?
因為我們知道這個服務器套接字上有一個傳入連接在等待,所以可以安全地接受它;也就是說,不用擔心 accept() 操作會阻塞:?
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();?
SocketChannel sc = ssc.accept();?
下一步是將新連接的 SocketChannel 配置為非阻塞的。而且由于接受這個連接的目的是為了讀取來自套接字的數據,所以我們還必須將 SocketChannel 注冊到 Selector上,如下所示:?
sc.configureBlocking(false);?
SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);?
注意使用 register() 的 OP_READ 參數,將 SocketChannel 注冊用于 讀取 而不是 接受 新連接。?
刪除處理過的 SelectionKey?
在處理 SelectionKey 之后,我們幾乎可以返回主循環了。但是我們必須首先將處理過的 SelectionKey 從選定的鍵集合中刪除。如果我們沒有刪除處理過的鍵,那么它仍然會在主集合中以一個激活的鍵出現,這會導致我們嘗試再次處理它。我們調用迭代器的 remove() 方法來刪除處理過的 SelectionKey:?
it.remove();?
現在我們可以返回主循環并接受從一個套接字中傳入的數據(或者一個傳入的 I/O 事件)了。?
傳入的 I/O?
當來自一個套接字的數據到達時,它會觸發一個 I/O 事件。這會導致在主循環中調用 Selector.select(),并返回一個或者多個 I/O 事件。這一次, SelectionKey 將被標記為 OP_READ 事件,如下所示:?
} else if (key.isReadable()) {?
???? // Read the data?
???? SocketChannel sc = (SocketChannel)key.channel();?
}?
與以前一樣,我們取得發生 I/O 事件的通道并處理它。?
回到主循環?
每次返回主循環,都要調用 select 的 Selector()方法,并取得一組 SelectionKey。每個鍵代表一個 I/O 事件。我們處理事件,從選定的鍵集中刪除 SelectionKey,然后返回主循環的頂部。?
這個程序有點過于簡單,因為它的目的只是展示異步 I/O 所涉及的技術。在現實的應用程序中,您需要通過將通道從 Selector 中刪除來處理關閉的通道。而且您可能要使用多個線程。這個程序可以僅使用一個線程,因為它只是一個演示,但是在現實場景中,創建一個線程池來負責 I/O 事件處理中的耗時部分會更有意義。?
轉載于:https://www.cnblogs.com/marcotan/p/4256936.html
總結
以上是生活随笔為你收集整理的ByteBuffer使用之道的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾达 I4 无线路由器带宽管理设置指南
- 下一篇: TP-Link TL-WR740N 无线