參考:
http://www.reibang.com/p/362b365e1bcc
http://www.reibang.com/p/5d2c68b89f1d
http://www.reibang.com/p/389e4571cd2c
一.NIO產(chǎn)生背景
-
高并發(fā)場(chǎng)景的技術(shù)要求
1)傳統(tǒng)IO即使使用線程池技術(shù),面臨高并發(fā)時(shí)依然線程不足
2)傳統(tǒng)阻塞I/O模式帝际,大量線程在等待數(shù)據(jù)時(shí)被掛起,CPU利用率低,系統(tǒng)吞吐量差
3)線程較長(zhǎng)阻塞時(shí)間在網(wǎng)絡(luò)不穩(wěn)定場(chǎng)景下手素,將降低系統(tǒng)可靠性 - 傳統(tǒng)IO特性
1)IO是面向流的祖搓、阻塞的
2)傳統(tǒng)IO模型中佑力,一個(gè)連接對(duì)應(yīng)一個(gè)線程
3)傳統(tǒng)IO面向流意味著連接每次從流中讀取一個(gè)或多個(gè)字節(jié)底桂,直至全部讀取完畢植袍,沒有緩存在任何地方,同時(shí)不能前后移動(dòng)流中數(shù)據(jù)
4)傳統(tǒng)IO的各種流是阻塞的戚啥,意味著當(dāng)線程調(diào)用read或write方法時(shí)將被阻塞奋单,不能執(zhí)行其他操作 - NIO特性
1)NIO是面向塊的锉试,非阻塞的
2)NIO面向塊意味著將把數(shù)據(jù)讀取到一個(gè)稍后處理的緩沖區(qū)中猫十,必要時(shí)可在緩沖區(qū)內(nèi)前后移動(dòng),增加數(shù)據(jù)處理的靈活性
3)NIO非阻塞模式使得線程從某個(gè)通道讀取數(shù)據(jù)或者向某個(gè)通道寫數(shù)據(jù)的過程中呆盖,遇到數(shù)據(jù)等待時(shí)不會(huì)掛起拖云,可執(zhí)行其他工作
4)NIO通過將多個(gè)Channel以事件注冊(cè)到一個(gè)Selector實(shí)現(xiàn)由一個(gè)線程處理多個(gè)請(qǐng)求
二.NIO核心實(shí)現(xiàn)
NIO核心API Channel,Buffer应又,Selector宙项。數(shù)據(jù)總是從Chanel讀取到Buffer,或從Buffer寫入Channel
1.通道Channel
- 可以同時(shí)進(jìn)行讀寫(從緩沖區(qū)讀數(shù)據(jù)株扛,或?qū)憯?shù)據(jù)到緩沖區(qū))
- 可以異步讀寫數(shù)據(jù)
2.緩沖區(qū)Buffer
- 本質(zhì)是一個(gè)可以寫入數(shù)據(jù)的內(nèi)存塊尤筐,可以再次讀取
- 讀寫數(shù)據(jù)一般遵循:
1)寫數(shù)據(jù)到Buffer
2)調(diào)用Buffer.flip()方法,將Buffer從寫模式切換到讀模式
3)從Buffer讀取數(shù)據(jù)
4)調(diào)用Buffer.clear() 或 Buffer.compat() 方法洞就,清空Buffer - Buffer常用標(biāo)志:
1)Buffer的大小/容量 - Capacity
2)Buffer當(dāng)前讀寫位置 - Position
3)Buffer中信息末尾位置 - Limit
Buffer讀寫模式下的Capacity盆繁、Position、Limit
3.Selector
-
多個(gè)Channel以事件的方式注冊(cè)到一個(gè)Selector旬蟋,實(shí)現(xiàn)一個(gè)線程處理多個(gè)請(qǐng)求
一個(gè)線程處理多個(gè)請(qǐng)求 -
調(diào)用Selector的select()或selectNow()方法時(shí)只返回有數(shù)據(jù)讀取的SelectableChannel實(shí)例
返回有數(shù)據(jù)讀取的Channel
三.NIO常用方法
1.Buffer類的flip油昂、clear、compact方法
本質(zhì)是設(shè)置控制Buffer狀態(tài)的position、limit冕碟、capacity三個(gè)變量
- flip方法
使Buffer從讀狀態(tài)轉(zhuǎn)為寫狀態(tài):當(dāng)前position設(shè)置為limit拦惋,并將position指向數(shù)據(jù)開始位置
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
flip方法變讀狀態(tài)為寫狀態(tài)
- clear方法
重設(shè)緩沖區(qū)以重新接收字符
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
clear方法將緩沖區(qū)position清零
- compact方法
與clear方法類似,但只清空已讀取的數(shù)據(jù)安寺,還未讀取的數(shù)據(jù)仍保留在緩沖區(qū)
2.Channel類
- configureBlocking()和register()
channel.configureBlocking(false); //設(shè)置channel為非阻塞
SelectionKey key =channel.register(selector,SelectionKey.OP_READ); //注冊(cè)channel
3.Selector類
- SelectionKey
注冊(cè)到Selector上的實(shí)例
1)register()方法注冊(cè)的事件類型有4種
Connect 某個(gè)Channel成功連接到另一服務(wù)器
Accept 某個(gè)ServerSocketChannel準(zhǔn)備好接收新連接
Read 某個(gè)Channel有數(shù)據(jù)可讀
Write 某個(gè)Channel等待寫數(shù)據(jù)
2)對(duì)應(yīng)于SelectionKey的常量
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
3)SelectionKey包含如下屬性
The interest set 感興趣的事件的集合
The ready set 已經(jīng)準(zhǔn)備就緒的操作的集合
The Channel
The Selector
An attached object(optional) 將對(duì)象或信息attach到SelectionKey以便識(shí)別 - select()
返回int值表示有多少通道已就緒厕妖,包含重載方法
1)int select():阻塞到至少有一個(gè)通道在注冊(cè)的事件上就緒
2)int select(long timeout):和select()一樣,但設(shè)定阻塞時(shí)間上限timeout毫秒
3)int selectNow():不會(huì)阻塞挑庶,不管什么通道就緒都立刻返回叹放。如果自從前一次select后沒有通道就緒,則返回0 - selectedKeys()
在調(diào)用select()獲取就緒通道數(shù)后挠羔,可通過selectedKeys()方法返回就緒的Channel井仰,之后可通過迭代SelectionKey獲得就緒的Channel
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();
}
- 注意
1)Selector對(duì)象并不會(huì)從自己的SelectedKey集合中自動(dòng)移除SelectedKey實(shí)例,需要在處理完一個(gè)Channel時(shí)調(diào)用keyIterator.remove()方法手動(dòng)移除破加,下一次Channel就緒時(shí)俱恶,Selector會(huì)再將它添加到SelectedKey集合中
2)SelectionKey.channel()方法返回的channel需要轉(zhuǎn)成具體要處理的類型,如ServerSocketChannel或SocketChannel等 - wakeup()
略 - open()和close()
使用Selector前調(diào)用Selector.open()打開Selector
使用Selector后調(diào)用Selector.close()關(guān)閉Selector并使注冊(cè)到該Selector上的所有SelectionKey實(shí)例無效范舀,但通道本身并不會(huì)關(guān)閉
四.NIO實(shí)踐
1.從文件讀取數(shù)據(jù)
- 讀數(shù)據(jù)步驟
1)從FileInputStream獲取Channel
2)創(chuàng)建Buffer
3)從Channel讀取數(shù)據(jù)到Buffer
FileInputStream fin = new FileInputStream("ReadTest.txt");
FileChanel fc = fim.getChannel(); //獲取通道
ByteBuffer buffer = ByteBuffer.allocate(1024); //創(chuàng)建緩沖區(qū)
fc.read(buffer); //從通道讀入數(shù)據(jù)到緩沖區(qū)
- 寫數(shù)據(jù)步驟
1)從FileOutputStream獲取Channel
2)創(chuàng)建Buffer合是,并將數(shù)據(jù)放入Buffer
3)把Buffer中數(shù)據(jù)寫入Channel
FileOutputStream fout = new FileOutputStream("WriteTest.txt");
FileChannel fc = fout.getChannel; //獲取通道
ByteBuffer buffer = ByteBuffer.allocate(1024); //創(chuàng)建緩沖區(qū)
for(int i = 0 ; i < message.length ; i++) {
buffer.put( message[i] );
} //將數(shù)據(jù)放入緩沖區(qū)
buffer.flip(); //切換緩沖區(qū)為寫模式
fc.write(buffer); //將緩沖區(qū)內(nèi)容寫入通道
- 讀寫結(jié)合例程
/**
* 用java NIO api拷貝文件
* @param src
* @param dst
* @throws IOException
*/
public static void copyFileUseNIO(String src,String dst) throws IOException{
//聲明源文件和目標(biāo)文件
FileInputStream fi=new FileInputStream(new File(src));
FileOutputStream fo=new FileOutputStream(new File(dst));
//獲得傳輸通道channel
FileChannel inChannel=fi.getChannel();
FileChannel outChannel=fo.getChannel();
//獲得容器buffer
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(true){
//判斷是否讀完文件
int eof =inChannel.read(buffer);
if(eof==-1){
break;
}
//重設(shè)一下buffer的position=0,limit=position
buffer.flip();
//開始寫
outChannel.write(buffer);
//寫完要重置buffer锭环,重設(shè)position=0,limit=capacity
buffer.clear();
}
inChannel.close();
outChannel.close();
fi.close();
fo.close();
}
2.網(wǎng)絡(luò)Socket使用NIO
步驟
例程
public void client() throws Exception{
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 3000));
socketChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String str = scanner.next();
buffer.put(str.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
public void server() throws Exception {
// 獲取通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 設(shè)置為非阻塞
serverChannel.configureBlocking(false);
// 綁定端口
serverChannel.bind(new InetSocketAddress(3000));
// 創(chuàng)建連接器
Selector selector = Selector.open();
// 將連接器注冊(cè)到channel聪全,并設(shè)置監(jiān)聽事件(接受事件)
// SelectionKey.OP_CONNECT:鏈接狀態(tài)
// SelectionKey.OP_READ:讀狀態(tài)
// SelectionKey.OP_WRITE:寫狀態(tài)
// SelectionKey.OP_ACCEPT:接受狀態(tài),當(dāng)接受準(zhǔn)備就緒辅辩,開始進(jìn)行下一步操作
// 通過 | 進(jìn)行鏈接可以監(jiān)聽多個(gè)狀態(tài)
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
// 輪尋獲取選擇器上已經(jīng)準(zhǔn)備就緒的狀態(tài)
while (selector.select() > 0) {
// 獲取所有的監(jiān)聽Key
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
// 若獲取狀態(tài)就緒难礼,就獲取客戶端的鏈接
SocketChannel clientChannel = serverChannel.accept();
// 將客戶端的鏈接設(shè)置為非阻塞狀態(tài)
clientChannel.configureBlocking(false);
// 給該通道注冊(cè)到選擇器上,并設(shè)置狀態(tài)為讀就緒
clientChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = channel.read(buffer)) > 0) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
}
// 取消處理完了的選擇建
iterator.remove();
}
}
}
愿將腰下劍,直為斬樓蘭