圖解Java NIO

目錄:

NIO結(jié)構(gòu)

NIO與傳統(tǒng)IO異同

NIO使用步驟

NIO代碼

ByteBuffer難點(diǎn)解析


1:NIO結(jié)構(gòu):

Channel:通道,連接客戶(hù)端和服務(wù)端的一個(gè)管道,管道內(nèi)可以雙向傳輸數(shù)據(jù)。

Selector:選擇器,可以想象成一個(gè)環(huán)狀傳送帶,上面可以接入很多管道,selector還可以對(duì)每個(gè)管道設(shè)置感興趣的顏色(連接(紅色),讀(黃色),寫(xiě)(藍(lán)色),接收數(shù)據(jù))。當(dāng)Selector開(kāi)始輪詢(xún)的時(shí)候Selector這個(gè)傳送帶就一直轉(zhuǎn)動(dòng),當(dāng)某個(gè)管道被傳送到感興趣事件檢查點(diǎn)的時(shí)候,selector會(huì)檢查改管道當(dāng)前顏色(即事件)之前是否被注冊(cè)成了感興趣顏色(事件),如果感興趣,那么Selector就可以對(duì)這個(gè)管道做處理了,比如把管道傳給別的線程,讓別的線程完成讀寫(xiě)操作。

ByteBuffer:字節(jié)緩沖區(qū),本質(zhì)上是一個(gè)連續(xù)的字節(jié)數(shù)組,Selector感興趣的事件發(fā)生后對(duì)管道的讀操作所讀到的數(shù)據(jù)都存儲(chǔ)在ByteBuffer中,而對(duì)管道的寫(xiě)操作也是以ByteBuffer為源頭,值得注意的是ByteBuffer可以有多個(gè)。想想看,我們使用的所有基本類(lèi)型都可以轉(zhuǎn)換成byte字節(jié),比如Integer類(lèi)型占4字節(jié),那么傳遞數(shù)字 1 ByteBuffer底層數(shù)組被占用了4個(gè)格子。


2:與傳統(tǒng)IO比較:

1:傳統(tǒng)IO一般是一個(gè)線程等待連接,連接過(guò)來(lái)之后分配給processor線程,processor線程與通道連接后如果通道沒(méi)有數(shù)據(jù)過(guò)來(lái)就會(huì)阻塞(線程被動(dòng)掛起)不能做別的事情。NIO則不同,首先:在Selector線程輪詢(xún)的過(guò)程中就已經(jīng)過(guò)濾掉了不感興趣的事件,其次:在processor處理感興趣事件的read和write都是非阻塞操作即直接返回的,線程沒(méi)有被掛起。

2:傳統(tǒng)IO的管道是單向的,NIO的管道是雙向的

3:兩者都是同步的,也就是Java程序親力親為的去讀寫(xiě)數(shù)據(jù),不管傳統(tǒng)IO還是NIo都需要read和write方法,這些都是Java程序調(diào)用的而不是系統(tǒng)幫我們調(diào)用的。NIO2.0里這點(diǎn)得到了改觀,即使用異步非阻塞AsynchronousXXX四個(gè)類(lèi)來(lái)處理。

3:使用NIO步驟:(服務(wù)端)

首先:創(chuàng)建一個(gè)傳送帶

然后:創(chuàng)建一個(gè)管道,設(shè)置管道為非阻塞,綁定端口

然后:把管道放到傳送帶上

再然后:?jiǎn)?dòng)傳送帶

其次:傳送帶感興趣事件檢查點(diǎn)查獲一個(gè)感興趣管道,轉(zhuǎn)給其他線程對(duì)管道進(jìn)行非阻塞讀寫(xiě)

最后:全使用完,關(guān)閉管道

過(guò)程很清晰,跟我們現(xiàn)實(shí)世界中的傳送帶效果一樣。


4:代碼體現(xiàn):

注意:只寫(xiě)服務(wù)器端關(guān)鍵步驟,客戶(hù)端可以參考這些代碼

public class Server implements Runnable {

private Selector selector;

private ByteBuffer buffer = ByteBuffer.allocate(1024);

public Server(int port) {

? ? ? ? try {

? ? ? ? ? ? //1 創(chuàng)建一個(gè)傳送帶

? ? ? ? ? ? selector = Selector.open();

? ? ? ? ? ? //2 創(chuàng)建一個(gè)管道

? ? ? ? ? ? ServerSocketChannel ssc = ServerSocketChannel.open();

? ? ? ? ? ? //3 設(shè)置服務(wù)器通道為非阻塞方式

? ? ? ? ? ? ssc.configureBlocking(false);

? ? ? ? ? ? //4 綁定TCP地址

? ? ? ? ? ? ssc.bind(new InetSocketAddress(port));

? ? ? ? ? ? //5 把管道放到傳送帶上,并在傳送帶上注冊(cè)一個(gè)感興趣事件,此處傳送帶感興趣事件為連接事件

? ? ? ? ? ? ssc.register(selector, SelectionKey.OP_ACCEPT);

? ? ? ? ? ? System.out.println("Server start, port:" + port);

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

public void run() {?

?while (true) {

? ? try {

? ? ? ? ? ? ? ? //1 啟動(dòng)傳送帶,開(kāi)始輪詢(xún)

? ? ? ? ? ? ? ? selector.select();

? ? ? ? ? ? ? ? //2 所有感興趣事件的keys

? ? ? ? ? ? ? ?SelectionKey Iterator keys = selector.selectedKeys().iterator();

? ? ? ? ? ? ? ? //3 遍歷所有感興趣事件集合

? ? ? ? ? ? ? ? while (keys.hasNext()) {

? ? ? ? ? ? ? ? ? ? SelectionKey key = keys.next();

? ? ? ? ? ? ? ? ? ? keys.remove();

? ? ? ? ? ? ? ? ? ? if(key.isValid()) { //如果key的狀態(tài)是有效的

? ? ? ? ? ? ? ? ? ? ? ? if(key.isAcceptable()) { //如果key是阻塞狀態(tài),則調(diào)用accept()方法

? ? ? ? ? ? ? ? ? ? ? ? ? ? accept(key);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? if(key.isReadable()) { //如果key是可讀狀態(tài),則調(diào)用read()方法

? ? ? ? ? ? ? ? ? ? ? ? ? ? read(key);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? } catch (IOException e) {

? ? ? ? ? ? ? ? e.printStackTrace();

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? private void accept(SelectionKey key) {

? ? ? ? try {

? ? ? ? ? ? //1 獲取服務(wù)器通道

? ? ? ? ? ? ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

? ? ? ? ? ? //2 執(zhí)行阻塞方法

? ? ? ? ? ? SocketChannel sc = ssc.accept();

? ? ? ? ? ? //3 設(shè)置阻塞模式為非阻塞

? ? ? ? ? ? sc.configureBlocking(false);

? ? ? ? ? ? //4 注冊(cè)到多路復(fù)用選擇器上,并設(shè)置讀取標(biāo)識(shí)

? ? ? ? ? ? sc.register(selector, SelectionKey.OP_READ);

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

? ? private void read(SelectionKey key) {

? ? ? ? try {

? ? ? ? ? ? //1 清空緩沖區(qū)中的舊數(shù)據(jù)

? ? ? ? ? ? buffer.clear();

? ? ? ? ? ? //2 獲取之前注冊(cè)的SocketChannel通道

? ? ? ? ? ? SocketChannel sc = (SocketChannel) key.channel();

? ? ? ? ? ? //3 將sc中的數(shù)據(jù)放入buffer中

? ? ? ? ? ? int count = sc.read(buffer);

? ? ? ? ? ? if(count == -1) { // == -1表示通道中沒(méi)有數(shù)據(jù)

? ? ? ? ? ? ? ? key.channel().close();

? ? ? ? ? ? ? ? key.cancel();

? ? ? ? ? ? ? ? return;

? ? ? ? ? ? }

? ? ? ? ? ? //讀取到了數(shù)據(jù),將buffer的position復(fù)位到0

? ? ? ? ? ? buffer.flip();

? ? ? ? ? ? byte[] bytes = new byte[buffer.remaining()];

? ? ? ? ? ? //將buffer中的數(shù)據(jù)寫(xiě)入byte[]中

? ? ? ? ? ? buffer.get(bytes);

? ? ? ? ? ? String body = new String(bytes).trim();

? ? ? ? ? ? System.out.println("Server:" + body);

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

? ? public static void main(String[] args) {

? ? ? ? new Thread(new Server(8379)).start();

? ? }

}

其他:

涉及到ByteBuffer分類(lèi)及ByteBuffer的讀寫(xiě)這里就不過(guò)多介紹了,就是一些指針和模式的變動(dòng),主要是flip方法,調(diào)用flip方法之后的變化從寫(xiě)模式變成讀模式


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言: 之前的文章《Java文件IO常用歸納》主要寫(xiě)了Java 標(biāo)準(zhǔn)IO要注意的細(xì)節(jié)和技巧,由于網(wǎng)上各種學(xué)習(xí)途徑,...
    androidjp閱讀 3,235評(píng)論 0 22
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,925評(píng)論 1 143
  • 轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,482評(píng)論 0 22
  • [TOC] > 從JDK NIO文檔可以看到,Java將其劃分成了三大塊:Channel,Buffer以及Sele...
    Ever_00閱讀 1,204評(píng)論 0 3
  • 文/洛羽 滿(mǎn)溺出一杯想念的思愁 那是風(fēng)對(duì)雨的思念 在午夜的燈光下 我滿(mǎn)上了一杯酒 嗅著空氣中彌漫的芬芳啊 靜靜的,...
    風(fēng)洛天羽閱讀 846評(píng)論 0 5

友情鏈接更多精彩內(nèi)容