參考鏈接:
Java NIO Tutorial
1. Java NIO 概覽:
NIO:Non-block IO,非阻塞IO。
Java NIO主要包括三個核心組件:
- Channels
- Buffers
- Selectors
Channels和Buffers
NIO中所有的IO都從一個Channel開始,Channel就像一個數(shù)據(jù)流誊册,我們可以從Channel中讀取數(shù)據(jù)到Buffer中仙蛉,也可以從Buffer通過Channel寫出數(shù)據(jù)材彪。如圖所示:
在NIO中有多種類型的Channel和Buffer您市,一些基本的Channel實現(xiàn)如下:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
可以看出循榆,這些Channel包含了TCP和UDP協(xié)議的網(wǎng)絡(luò)IO,以及文件IO墨坚。
接下來列出一些NIO中的核心Buffer:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
你可以通過這些Buffer傳送byte秧饮,character,等基本類型的數(shù)據(jù)泽篮。
Selectors
NIO中通過Selector負責(zé)監(jiān)聽多個Channel的事件盗尸,例如連接建立,數(shù)據(jù)到達等帽撑,這樣一來泼各,我們就可以在單線程中非阻塞地處理多個Channel中的數(shù)據(jù)。
我們會在每個Selector中注冊一個或多個Channel亏拉,調(diào)用selector的select()方法進行事件處理扣蜻。
2. Channel
四種Channel:
FileChannel : 從文件讀寫數(shù)據(jù)
DatagramChannel : 讀寫通過UDP協(xié)議傳輸?shù)木W(wǎng)絡(luò)數(shù)據(jù)
SocketChannel:處理通過TCP協(xié)議傳輸?shù)木W(wǎng)絡(luò)數(shù)據(jù)
-
ServerSocketChannel:使得我們能夠監(jiān)聽到達服務(wù)器的TCP連接請求,就像Web服務(wù)器一樣及塘。每有一個鏈接到來就會創(chuàng)建并分配一個 SocketChannel對象莽使。
下面的代碼展示了一個使用FileChannel將一些數(shù)據(jù)寫入到一個ByteBuffer中:
Java
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {System.out.println("Read " + bytesRead); buf.flip(); while(buf.hasRemaining()){ System.out.print((char) buf.get()); } buf.clear(); bytesRead = inChannel.read(buf);
}
aFile.close();
## 3. Buffer
buffer 其實就是一塊你可以進行讀寫數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被NIO Buffer對象封裝笙僚,該對象提供一系列API使得處理內(nèi)存更加容易芳肌。
使用Buffer進行數(shù)據(jù)讀寫一般要遵循以下4步:
- 寫數(shù)據(jù)到buffer中
- 調(diào)用buffer.flip()將buffer從寫入模式改為讀取模式
- 讀數(shù)據(jù)
- 調(diào)用buffer.clear()清空數(shù)據(jù)或者buffer.compact()清除你所讀取的那部分數(shù)據(jù)
下面是一個栗子:
```Java```
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.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();
4.Selector
如下代碼可以創(chuàng)建一個Selector:
Selector selector = Selector.open()
將Selector注冊到Channel:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
我們注意到注冊方法第二個參數(shù)代表著一個讀取操作,實際上進行Selector注冊的時候我們有4種事件可以選擇監(jiān)聽:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
SelectionKey:
當(dāng)將一個Channel注冊到一個Selector上的時候 register() 方法會返回一個 SelectionKey對象肋层。該對象包括一些屬性:
- interest Set
代表你在注冊Channel的時候所感興趣的事件:
Java
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
- ReadySet
代表一個Channel已經(jīng)準(zhǔn)備好的操作集合:
```Java```
int readySet = selectionKey.readyOps();
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
- Channel + Selector
通過SelectionKey訪問Channel和Selector是非常容易的:
Java
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
- Attaching Objects
我們可以在SelectionKey上附加一個對象來儲存更多的信息亿笤。
```Java```
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
通過Selector選取一個Channel
當(dāng)你為Selector注冊了一個或者多個Channel后你就可以通過select()方法選取已經(jīng)ready的Channels(注意s).select方法有以下三種:
- int select() ; 如果當(dāng)前沒有已經(jīng)ready的Channel供選擇,該方法阻塞栋猖,知道某個Channel是ready狀態(tài)净薛,將它選取。
- int select(long timeout) ; 超時時間timeout內(nèi)會進行等待蒲拉。
- int selectNow() ; 不會阻塞肃拜,不管有沒有Channel準(zhǔn)備好,都會立即返回全陨。
select 方法返回的int值爆班,代表了當(dāng)前有多少個Channel是ready的。注意該值是指從你上一次調(diào)用select后辱姨,有多少個Channel變成了ready狀態(tài)。
//selectedKeys, to supplement
一個完整的selector工作示例:
Java
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}