java NIO詳解

概述

BIO是面向字節(jié)流和字符流的辉巡,數(shù)據(jù)從流中順序獲取

NIO是面向通道和緩沖區(qū)的溅固,數(shù)據(jù)總是從通道中讀到buffer緩沖區(qū)內(nèi)募书,或者從buffer緩沖區(qū)內(nèi)寫入通道

Channel通道和Buffer緩沖區(qū)是NIO的核心异雁,幾乎在每一個(gè)IO操作中使用它們华临,Selector選擇器則允許單個(gè)線程操作多個(gè)通道,對(duì)于高并發(fā)多連接很有幫助饥追。

操作系統(tǒng)的IO一般分為兩個(gè)階段图仓,等待和就緒操作,比如讀可以分為等待系統(tǒng)可讀和真正的讀但绕,寫可以分為等待系統(tǒng)可寫和真正的寫救崔,在傳統(tǒng)的BIO中是這兩個(gè)階段都會(huì)阻塞,在NIO中第一個(gè)階段不是阻塞的捏顺,第二個(gè)階段是阻塞的六孵,如下圖,BIO是阻塞IO幅骄,NIO是非阻塞IO

nio.jpg

Buffer(緩沖區(qū))

常用Buffer類型

ByteBuffer,MappedByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer劫窒。對(duì)應(yīng)了幾大基本數(shù)據(jù)類型

ByteBuffer可以選擇實(shí)例化為DirectByteBuffer和HeapByteBuffer,如果數(shù)據(jù)量比較小的中小應(yīng)用情況下拆座,可以考慮使用heapBuffer主巍;反之可以用directBuffer。一般來(lái)說(shuō)DirectByteBuffer可以減少一次系統(tǒng)空間到用戶空間的拷貝挪凑。但Buffer創(chuàng)建和銷毀的成本更高孕索,更不宜維護(hù),通常會(huì)用內(nèi)存池來(lái)提高性能躏碳。其他buffer類似

利用Buffer讀寫數(shù)據(jù)步驟

  • 將數(shù)據(jù)寫入buffer

  • 調(diào)用flip將寫模式改為讀模式

  • 從buffer中讀取數(shù)據(jù)搞旭,進(jìn)行操作

  • 調(diào)用clear()清除整個(gè)buffer數(shù)據(jù)或者調(diào)用compact()清空已讀數(shù)據(jù)

buffer的屬性

  • capacity容量,該buffer最多存儲(chǔ)的字節(jié)數(shù)

  • position位置菇绵,寫模式下选脊,position從0到capacity-1,變更為讀模式后脸甘,position歸零恳啥,邊讀邊移動(dòng)

  • limit限制,寫模式下丹诀,代表我們能寫的最大量為capacity钝的,讀模式下,變更為原position位置铆遭,即有數(shù)據(jù)的位置

buffer_modes.png

buffer api

  • 通過(guò)allocate()方法為buffer分配內(nèi)存大小硝桩,如開(kāi)辟一個(gè)48字節(jié)的ByteBuffer buffer:ByteBuffer.allocate(48)

  • 寫數(shù)據(jù)可以通過(guò)通道寫,如:FileChannel.read(buffer)枚荣;也可以通過(guò)put方法來(lái)寫數(shù)據(jù)碗脊,如:buffer.put(127)

  • flip()翻轉(zhuǎn)方法,將寫模式切換到讀模式橄妆,position歸零衙伶,設(shè)置limit為之前的position位置

  • 讀數(shù)據(jù)可以讀到通道祈坠,如:FileChannel.write(buffer);也可以調(diào)用get()方法讀取矢劲,byte aByte=buffer.get()

  • buffer.rewind()將position置0赦拘,limit不變,這樣我們就可以重復(fù)讀取數(shù)據(jù)啦

  • buffer.clear()將position置為0芬沉,limit設(shè)置為capacity躺同,這里并沒(méi)有刪除buffer里面的數(shù)據(jù),只是把標(biāo)記位置改了丸逸;

  • buffer.compact()清除已讀數(shù)據(jù)蹋艺,這里也沒(méi)有刪除數(shù)據(jù),將position設(shè)置為未讀的個(gè)數(shù)黄刚,將后面幾個(gè)未讀的字節(jié)順序的復(fù)制到前面的幾個(gè)字節(jié)捎谨,limit設(shè)置為capacity,比如buffer容量3個(gè)字節(jié)隘击,讀取hello,在讀取了2個(gè)字節(jié)后我就調(diào)用了compact()方法研铆,那么此時(shí)position為1埋同,limit為3,buffer內(nèi)部存儲(chǔ)的數(shù)據(jù)buff[0]='l',buff[1]='e',buff[2]='l'棵红,因?yàn)橛幸粋€(gè)'l'沒(méi)有讀完凶赁,將'l'提取到最前面供下次讀取

  • mark()可以標(biāo)記當(dāng)前的position位置,通過(guò)reset來(lái)恢復(fù)mark位置逆甜,可以用來(lái)實(shí)現(xiàn)重復(fù)讀取滿足條件的數(shù)據(jù)塊

  • equals()兩個(gè)buffer相等需滿足虱肄,類型相同,buffer剩余(未讀)字節(jié)數(shù)相同交煞,所有剩余字節(jié)數(shù)相同

  • compareTo()比較buffer中的剩余元素咏窿,只不過(guò)此方法適合排序

Channel(通道)

Channel的重要實(shí)現(xiàn)

  • FileChannel用于文件數(shù)據(jù)的讀寫,transferTo()方法可以將通道的數(shù)據(jù)傳送至另外一個(gè)通道素征,完成數(shù)據(jù)的復(fù)制

  • DatagramChannel用于UDP數(shù)據(jù)的讀寫

  • SocketChannel用于TCP的數(shù)據(jù)讀寫集嵌,通常我們所說(shuō)的客戶端套接字通道

  • ServerSocketChannel允許我們監(jiān)聽(tīng)TCP鏈接請(qǐng)求,通常我們所說(shuō)的服務(wù)端套接字通道御毅,每一個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)SocketChannel

Scatter和Gather

  • java nio在Channel實(shí)現(xiàn)類也實(shí)現(xiàn)了Scatter和Gather相關(guān)類

  • Scatter.read()是從通道讀取的操作能把數(shù)據(jù)寫入多個(gè)buffer根欧,即一個(gè)通道向多個(gè)buffer寫數(shù)據(jù)的過(guò)程,但是read必須寫滿一個(gè)buffer后才會(huì)向后移動(dòng)到下一個(gè)buffer端蛆,因此read不適合大小會(huì)動(dòng)態(tài)改變的數(shù)據(jù)凤粗。代碼如下:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
  • Gather.write()是從可以把多個(gè)buffer的數(shù)據(jù)寫入通道,write是只會(huì)寫position到limit之間的數(shù)據(jù)今豆,因此寫是可以適應(yīng)大小動(dòng)態(tài)改變的數(shù)據(jù)嫌拣。代碼如下:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

在有些場(chǎng)景會(huì)非常有用柔袁,比如處理多份需要分開(kāi)傳輸?shù)臄?shù)據(jù),舉例來(lái)說(shuō)亭罪,假設(shè)一個(gè)消息包含了header和body瘦馍,我們可能會(huì)把header和body分別放在不同的buffer

Selector(選擇器)

Selector用于檢查一個(gè)或多個(gè)NIO Channel的狀態(tài)是否可讀可寫,這樣就可以實(shí)現(xiàn)單線程管理多個(gè)Channels应役,也就是可以管理多個(gè)網(wǎng)絡(luò)連接情组,NIO非阻塞主要就是通過(guò)Selector注冊(cè)事件監(jiān)聽(tīng),監(jiān)聽(tīng)通道將數(shù)據(jù)就緒后箩祥,就進(jìn)行實(shí)際的讀寫操作院崇,因此前面說(shuō)的IO兩個(gè)階段,一階段NIO僅僅是異步監(jiān)聽(tīng)袍祖,二階段就是同步實(shí)際操作數(shù)據(jù)

Selector監(jiān)聽(tīng)事件類別

  • SelectionKey.OP_CONNECT是Channel和server連接成功后底瓣,連接就緒

  • SelectionKey.OP_ACCEPT是server Channel接收請(qǐng)求連接就緒

  • SelectionKey.OP_READ是Channel有數(shù)據(jù)可讀時(shí),處于讀就緒

  • SelectionKey.OP_WRITE是Channel可以進(jìn)行數(shù)據(jù)寫入是蕉陋,寫就緒

使用Selector的步驟

  • 創(chuàng)建一個(gè)Selector捐凭,Selector selector = Selector.open();

  • 注冊(cè)Channel到Selector上面,將Channel切換為非阻塞的:channel.configureBlocking(false)凳鬓,然后綁定Selector:SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

  • SelectionKey.OP_READ為監(jiān)聽(tīng)的事件類別茁肠,如果要監(jiān)聽(tīng)多個(gè)事件,可利用位的或運(yùn)算結(jié)合多個(gè)常量如:int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

需要使用Selector的Channel必須是非阻塞的缩举,F(xiàn)ileChannel不能切換為非租塞的垦梆,因此FileChannel不適用于Selector

API

  • Selector.select()方法是阻塞的,因此可以放心的將它寫在while(true)中仅孩,不用擔(dān)心cpu會(huì)空轉(zhuǎn)

  • Selector.wakeup()喚醒select()造成的阻塞托猩,可能是有新的事件注冊(cè),優(yōu)先級(jí)更高的事件觸發(fā)(如定時(shí)器事件)辽慕,希望及時(shí)處理京腥。其原理是向通道或者連接中寫入一個(gè)字節(jié),阻塞的select因?yàn)橛蠭O事件就緒溅蛉,立即返回

Issues

  • 在使用TcpClient和TcpServer時(shí)绞旅,最開(kāi)始客戶端結(jié)束之后,服務(wù)端catch住了異常温艇,但是并沒(méi)有處理因悲,那個(gè)客戶端的Channel還是可以從Selector出來(lái),原因是注冊(cè)了這個(gè)事件沒(méi)有取消注冊(cè)勺爱,在catch中取消注冊(cè)該事件就行晃琳,SelectionKey.cancel()

  • localhost是一個(gè)域名,通常指向127.0.0.1可在host文件配置

  • 127.0.0.1指本機(jī)地址環(huán)回地址,只有自己的機(jī)器可以訪問(wèn)

  • 0.0.0.0表示網(wǎng)絡(luò)中的本機(jī)地址卫旱,即如果本機(jī)器連接了兩個(gè)網(wǎng)絡(luò)人灼,在注冊(cè)服務(wù)端時(shí)使用0.0.0.0,這兩個(gè)網(wǎng)絡(luò)的其他主機(jī)都可以訪問(wèn)這個(gè)服務(wù)端

我們可以理解為本機(jī)有三塊網(wǎng)卡顾翼,一塊網(wǎng)卡叫做 loopback(這是一塊虛擬網(wǎng)卡)127.*整個(gè)網(wǎng)段用作loopback網(wǎng)絡(luò)接口的默認(rèn)地址投放,按照慣例通常設(shè)置為127.0.0.1這個(gè)地址和網(wǎng)絡(luò)無(wú)關(guān),這個(gè)是環(huán)回地址适贸,直接找本地地址灸芳,如果服務(wù)端綁定這個(gè)地址,那就只有本地服務(wù)器才可以訪問(wèn)這個(gè)服務(wù)端拜姿,另外一塊網(wǎng)卡叫做 ethernet (這是你的有線網(wǎng)卡)烙样,還有一塊網(wǎng)卡叫做 wlan(這是你的無(wú)線網(wǎng)卡)

實(shí)例源代碼如下圖:前往下載

demo.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蕊肥,隨后出現(xiàn)的幾起案子谒获,更是在濱河造成了極大的恐慌,老刑警劉巖壁却,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件批狱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡展东,警方通過(guò)查閱死者的電腦和手機(jī)赔硫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)琅锻,“玉大人卦停,你說(shuō)我怎么就攤上這事向胡∧张睿” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵僵芹,是天一觀的道長(zhǎng)处硬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拇派,這世上最難降的妖魔是什么荷辕? 我笑而不...
    開(kāi)封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮件豌,結(jié)果婚禮上疮方,老公的妹妹穿的比我還像新娘。我一直安慰自己茧彤,他們只是感情好骡显,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般惫谤。 火紅的嫁衣襯著肌膚如雪壁顶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天溜歪,我揣著相機(jī)與錄音若专,去河邊找鬼。 笑死蝴猪,一個(gè)胖子當(dāng)著我的面吹牛调衰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拯腮,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼窖式,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了动壤?” 一聲冷哼從身側(cè)響起萝喘,我...
    開(kāi)封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎琼懊,沒(méi)想到半個(gè)月后阁簸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哼丈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年启妹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片醉旦。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饶米,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出车胡,到底是詐尸還是另有隱情檬输,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布匈棘,位于F島的核電站丧慈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏主卫。R本人自食惡果不足惜逃默,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望簇搅。 院中可真熱鬧完域,春花似錦、人聲如沸瘩将。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至乌妙,卻和暖如春使兔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藤韵。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工虐沥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泽艘。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓欲险,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親匹涮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子天试,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容