前段時(shí)間在項(xiàng)目組分享了下NIO,現(xiàn)重新整理一下
一.概念
什么是NIO,和傳統(tǒng)IO有什么區(qū)別:
- java NIO全稱java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,為所有的原始類型(boolean類型除外)提供緩存支持的數(shù)據(jù)容器,使用它可以提供非阻塞式的高伸縮性網(wǎng)絡(luò)。(百度百科)
- 主要區(qū)別
| IO | NIO |
|---|---|
| 面向流 | 緩沖區(qū) |
| 阻塞 | 非阻塞 |
| -- | 多路復(fù)用 |
阻塞IO和非阻塞IO
https://www.cnblogs.com/aspirant/p/6877350.htmlutm_source=itdadao&utm_medium=referral
這篇博文介紹的比較清楚.
二.核心組件
- Channels(通道)
- Buffers(緩沖區(qū))
- Selectors(選擇器)
三.Buffer(緩沖區(qū))
Java NIO的Buffer用于和NIO通道進(jìn)行交互,緩沖區(qū)本質(zhì)上可以寫入數(shù)據(jù),讀取數(shù)據(jù)的內(nèi)存,這塊內(nèi)存被包裝成Buffer對象
數(shù)據(jù)都是通過通道(Channel)讀入緩沖區(qū),從緩沖區(qū)寫入通道
屬性
Capacity(容量):緩沖區(qū)能夠容納的元素的最大數(shù)量,初始化時(shí)就被設(shè)定
Limit(上界) :緩沖區(qū)的第一個(gè)不能被讀或?qū)懙脑?緩沖區(qū)中現(xiàn)存元素的個(gè)數(shù))
Position(位置):下一個(gè)要被讀或?qū)懙脑厮饕?位置會(huì)自動(dòng)由get()和put()更新
Mark(標(biāo)記):一個(gè)備忘位置
APi
put() 寫數(shù)據(jù)
get() 讀取
flip()翻轉(zhuǎn):將一個(gè)能夠繼續(xù)添加元素的緩沖區(qū)翻轉(zhuǎn)成一個(gè)準(zhǔn)備讀出元素的釋放狀態(tài)
hasRemaining():函數(shù)會(huì)在釋放緩沖區(qū)時(shí)告訴是否已經(jīng)達(dá)到緩沖區(qū)的上界
clear 回到初始狀態(tài)
mark()標(biāo)記, reset()回滾
ByteBuffer.allocate(10); 創(chuàng)建 底層都是數(shù)組
ByteBuffer.allocateDirect(10); 創(chuàng)建 底層不是數(shù)組
wrap(new byte[10]); 傳入byte數(shù)組,使用整個(gè)數(shù)組創(chuàng)建
wrap(new byte[10],2,3); 傳入byte數(shù)組,使用數(shù)組部分創(chuàng)建
compact 將未讀數(shù)據(jù)前移,positom指向前移后數(shù)據(jù)的后一位,limit = capacity;一次沒有讀完,切換到寫模式
四.Channel(通道)
Java nio的通道類似流,但又有不同:既可以從通道中讀取數(shù)據(jù),又可以寫數(shù)據(jù)到通道,但是流的讀寫通常時(shí)單向的通道可以異步去讀寫通道中的數(shù)據(jù)總是要先讀到一個(gè)Buffer,或總是要從一個(gè)Buffer中寫入
主要實(shí)現(xiàn):
FileChannel : 從文件中讀寫數(shù)據(jù)
DatagramChannel: 通過UDP讀寫網(wǎng)絡(luò)數(shù)據(jù)
SocketChannel:通過TCP讀寫網(wǎng)絡(luò)數(shù)據(jù)
ServerSocketChannel:監(jiān)聽新進(jìn)來的tcp連接
通道讀是將數(shù)據(jù)從通道讀到buffer里面;
通道寫是將數(shù)據(jù)從buffer寫入到通道;
五.selector(選擇器)
選擇器是java NIO中能夠檢測一道多個(gè)NIO通道,并能夠知曉通道是否為讀寫事件做好準(zhǔn)備的組件一個(gè)單獨(dú)的線程可以管理多個(gè)channel
selector使用:
- 創(chuàng)建selector
Selector selector = Selector.open();
- 注冊channel到selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector,SelectionKey.OP_READ );
與Selector一起使用時(shí),Channel必須處于非阻塞模式下。這意味著不能將 FileChannel與Selector一起使用,因?yàn)镕ileChannel不能切換到非阻塞模式。而套接字通道都可以。
- 通道選擇
int count = selector.select();
- 訪問“已選擇鍵集(selected key set)”中的就緒通道
Set selectedKeys = selector.selectedKeys();
注意register()方法的第二個(gè)參數(shù)。這是一個(gè)“interest集合”,意思是在通過Selector監(jiān)聽Channel時(shí)對什么事件感興趣??梢员O(jiān)聽四種不同類型的事件:
1. Connect
2. Accept
3. Read
4. Write
SelectionKeys
屬性
- slector() 獲取對象本身
- channel() 獲取當(dāng)前就緒的通道
- cancel() 取消注冊事件(通道)
- interst(int ops)注冊感興趣事件
- attachment() 附加對象

/**
* @ClassName NioServer
* @Description TODO
* @Author liuzetian
* @Date 2019/5/5 17:55 PM
* @Version 1.0
**/
public class NioServer {
private static Selector selector;
private static ServerSocketChannel serverSocketChannel;
private static ByteBuffer bf = ByteBuffer.allocate(1024);
public static void main(String[] args) throws Exception {
//創(chuàng)建selector
selector = Selector.open();
//打開serverSocketChannel通道
serverSocketChannel = ServerSocketChannel.open();
//設(shè)置為非阻塞模式
serverSocketChannel.configureBlocking(false);
//開啟綁定端口
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
//向selector注冊
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//count為準(zhǔn)備就緒通道的數(shù)量 阻塞
int count = selector.select();
//已選擇鍵集
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
//遍歷
while (it.hasNext()) {
SelectionKey key = it.next();
//連接狀態(tài)
if (key.isAcceptable()) {
System.out.println("新連接進(jìn)入");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
//數(shù)據(jù)可讀狀態(tài)
System.out.println("讀準(zhǔn)備就緒,開始讀.......................");
//獲取SocketChannel 讀取數(shù)據(jù)
SocketChannel channel = (SocketChannel) key.channel();
System.out.println("客戶端的數(shù)據(jù)如下:");
int readLen = 0;
bf.clear();
StringBuffer sb = new StringBuffer();
//注意一定要讀取完數(shù)據(jù)然后關(guān)閉通道,
while ((readLen = channel.read(bf)) > 0) {
bf.flip();
byte[] temp = new byte[readLen];
bf.get(temp, 0, readLen);
sb.append(new String(temp));
bf.clear();
}
if (-1 == readLen) {
channel.close();
}
System.out.println(sb.toString());
//注冊寫事件
channel.register(selector, SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
//寫事件
SocketChannel channel = (SocketChannel) key.channel();
channel.write(ByteBuffer.wrap(("客戶端,已接收到傳送來的數(shù)據(jù)").getBytes()));
//注銷寫事件
key.cancel();
// channel.close();
}
it.remove();
}
}
}
}
介紹的比較簡單, 如有錯(cuò)誤, 歡迎指正