面試題:關(guān)于一些nio的問題資料

Java NIO提供了與標準IO不同的IO工作方式:

Channels and Buffers(通道和緩沖區(qū)):標準的IO基于字節(jié)流和字符流進行操作的,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進行操作倔韭,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中航棱,或者從緩沖區(qū)寫入到通道中。

Asynchronous IO(異步IO):Java NIO可以讓你異步的使用IO靠胜,例如:當線程從通道讀取數(shù)據(jù)到緩沖區(qū)時掉瞳,線程還是可以進行其他事情。當數(shù)據(jù)被寫入到緩沖區(qū)時浪漠,線程可以繼續(xù)處理它陕习。從緩沖區(qū)寫入通道也類似。

Selectors(選擇器):Java NIO引入了選擇器的概念郑藏,選擇器用于監(jiān)聽多個通道的事件(比如:連接打開衡查,數(shù)據(jù)到達)。因此必盖,單個的線程可以監(jiān)聽多個數(shù)據(jù)通道拌牲。

下面就來詳細介紹Java NIO的相關(guān)知識。

Java NIO 概述

(本部分原文鏈接歌粥,作者:Jakob Jenkov塌忽, 譯者:airu,校對:丁一)

Java NIO 由以下幾個核心部分組成:

Channels

Buffers

Selectors

雖然Java NIO 中除此之外還有很多類和組件失驶,但在我看來土居,Channel,Buffer 和 Selector 構(gòu)成了核心的API嬉探。其它組件擦耀,如Pipe和FileLock,只不過是與三個核心組件共同使用的工具類涩堤。因此眷蜓,在概述中我將集中在這三個組件上。其它組件會在單獨的章節(jié)中講到胎围。

Channel 和 Buffer

基本上吁系,所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流白魂。 數(shù)據(jù)可以從Channel讀到Buffer中汽纤,也可以從Buffer 寫到Channel中。這里有個圖示:

Channel和Buffer有好幾種類型福荸。下面是JAVA NIO中的一些主要Channel的實現(xiàn):

FileChannel

DatagramChannel

SocketChannel

ServerSocketChannel

正如你所看到的蕴坪,這些通道涵蓋了UDP 和 TCP 網(wǎng)絡IO,以及文件IO敬锐。

與這些類一起的有一些有趣的接口背传,但為簡單起見捆等,我盡量在概述中不提到它們。本教程其它章節(jié)與它們相關(guān)的地方我會進行解釋续室。

以下是Java NIO里關(guān)鍵的Buffer實現(xiàn):

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

這些Buffer覆蓋了你能通過IO發(fā)送的基本數(shù)據(jù)類型:byte, short, int, long, float, double 和 char栋烤。

Java NIO 還有個 Mappedyteuffer,用于表示內(nèi)存映射文件挺狰, 我也不打算在概述中說明明郭。

Selector

Selector允許單線程處理多個 Channel。如果你的應用打開了多個連接(通道)丰泊,但每個連接的流量都很低薯定,使用Selector就會很方便。例如瞳购,在一個聊天服務器中话侄。

這是在一個單線程中使用一個Selector處理3個Channel的圖示:

要使用Selector,得向Selector注冊Channel学赛,然后調(diào)用它的select()方法年堆。這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回盏浇,線程就可以處理這些事件变丧,事件的例子有如新連接進來,數(shù)據(jù)接收等绢掰。

Java NIO vs. IO

(本部分原文地址痒蓬,作者:Jakob Jenkov,譯者:郭蕾滴劲,校對:方騰飛)

當學習了Java NIO和IO的API后攻晒,一個問題馬上涌入腦海:

引用

我應該何時使用IO,何時使用NIO呢班挖?在本文中鲁捏,我會盡量清晰地解析Java NIO和IO的差異、它們的使用場景聪姿,以及它們?nèi)绾斡绊懩拇a設(shè)計碴萧。

Java NIO和IO的主要區(qū)別

下表總結(jié)了Java NIO和IO之間的主要差別乙嘀,我會更詳細地描述表中每部分的差異末购。

IONIO

Stream orientedBuffer oriented

Blocking IONon blocking IO

?Selectors

面向流與面向緩沖

Java NIO和IO之間第一個最大的區(qū)別是,IO是面向流的虎谢,NIO是面向緩沖區(qū)的盟榴。 Java IO面向流意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié)婴噩,它們沒有被緩存在任何地方擎场。此外羽德,它不能前后移動流中的數(shù)據(jù)。如果需要前后移動從流中讀取的數(shù)據(jù)迅办,需要先將它緩存到一個緩沖區(qū)宅静。 Java NIO的緩沖導向方法略有不同。數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū)站欺,需要時可在緩沖區(qū)中前后移動姨夹。這就增加了處理過程中的靈活性。但是矾策,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)磷账。而且,需確保當更多的數(shù)據(jù)讀入緩沖區(qū)時贾虽,不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)逃糟。

阻塞與非阻塞IO

Java IO的各種流是阻塞的。這意味著蓬豁,當一個線程調(diào)用read() 或 write()時绰咽,該線程被阻塞,直到有一些數(shù)據(jù)被讀取地粪,或數(shù)據(jù)完全寫入剃诅。該線程在此期間不能再干任何事情了。 Java NIO的非阻塞模式驶忌,使一個線程從某通道發(fā)送請求讀取數(shù)據(jù)矛辕,但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時付魔,就什么都不會獲取聊品。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前几苍,該線程可以繼續(xù)做其他的事情翻屈。 非阻塞寫也是如此。一個線程請求寫入一些數(shù)據(jù)到某通道妻坝,但不需要等待它完全寫入伸眶,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執(zhí)行IO操作刽宪,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道(channel)厘贼。

選擇器(Selectors)

Java NIO的選擇器允許一個單獨的線程來監(jiān)視多個輸入通道,你可以注冊多個通道使用一個選擇器圣拄,然后使用一個單獨的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入嘴秸,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道岳掐。

NIO和IO如何影響應用程序的設(shè)計

無論您選擇IO或NIO工具箱凭疮,可能會影響您應用程序設(shè)計的以下幾個方面:

對NIO或IO類的API調(diào)用。

數(shù)據(jù)處理串述。

用來處理數(shù)據(jù)的線程數(shù)执解。

API調(diào)用

當然,使用NIO的API調(diào)用時看起來與使用IO時有所不同纲酗,但這并不意外材鹦,因為并不是僅從一個InputStream逐字節(jié)讀取,而是數(shù)據(jù)必須先讀入緩沖區(qū)再處理耕姊。

數(shù)據(jù)處理

使用純粹的NIO設(shè)計相較IO設(shè)計桶唐,數(shù)據(jù)處理也受到影響。

在IO設(shè)計中茉兰,我們從InputStream或 Reader逐字節(jié)讀取數(shù)據(jù)尤泽。假設(shè)你正在處理一基于行的文本數(shù)據(jù)流,例如:

代碼?

Name:?Anna??

Age:25

Email:?anna@mailserver.com??

Phone:1234567890

該文本行的流可以這樣處理:

Java代碼?

InputStream?input?=?…?;//?get?the?InputStream?from?the?client?socket

BufferedReader?reader?=newBufferedReader(newInputStreamReader(input));


String?nameLine???=?reader.readLine();??

String?ageLine????=?reader.readLine();??

String?emailLine??=?reader.readLine();??

String?phoneLine??=?reader.readLine();??

請注意處理狀態(tài)由程序執(zhí)行多久決定规脸。換句話說坯约,一旦reader.readLine()方法返回,你就知道肯定文本行就已讀完莫鸭, readline()阻塞直到整行讀完闹丐,這就是原因。你也知道此行包含名稱被因;同樣卿拴,第二個readline()調(diào)用返回的時候,你知道這行包含年齡等梨与。 正如你可以看到堕花,該處理程序僅在有新數(shù)據(jù)讀入時運行,并知道每步的數(shù)據(jù)是什么粥鞋。一旦正在運行的線程已處理過讀入的某些數(shù)據(jù)缘挽,該線程不會再回退數(shù)據(jù)(大多如此)。下圖也說明了這條原則:

從一個阻塞的流中讀數(shù)據(jù)

而一個NIO的實現(xiàn)會有所不同呻粹,下面是一個簡單的例子:

Java代碼?

ByteBuffer?buffer?=?ByteBuffer.allocate(48);


intbytesRead?=?inChannel.read(buffer);

注意第二行壕曼,從通道讀取字節(jié)到ByteBuffer。當這個方法調(diào)用返回時等浊,你不知道你所需的所有數(shù)據(jù)是否在緩沖區(qū)內(nèi)腮郊。你所知道的是,該緩沖區(qū)包含一些字節(jié)凿掂,這使得處理有點困難伴榔。

假設(shè)第一次 read(buffer)調(diào)用后,讀入緩沖區(qū)的數(shù)據(jù)只有半行庄萎,例如踪少,“Name:An”,你能處理數(shù)據(jù)嗎糠涛?顯然不能援奢,需要等待,直到整行數(shù)據(jù)讀入緩存忍捡,在此之前集漾,對數(shù)據(jù)的任何處理毫無意義。

所以砸脊,你怎么知道是否該緩沖區(qū)包含足夠的數(shù)據(jù)可以處理呢具篇?好了,你不知道凌埂。發(fā)現(xiàn)的方法只能查看緩沖區(qū)中的數(shù)據(jù)驱显。其結(jié)果是,在你知道所有數(shù)據(jù)都在緩沖區(qū)里之前瞳抓,你必須檢查幾次緩沖區(qū)的數(shù)據(jù)埃疫。這不僅效率低下,而且可以使程序設(shè)計方案雜亂不堪孩哑。例如:

Java代碼?

ByteBuffer?buffer?=?ByteBuffer.allocate(48);

intbytesRead?=?inChannel.read(buffer);

while(!?bufferFull(bytesRead)?)?{

bytesRead?=?inChannel.read(buffer);??

}??

bufferFull()方法必須跟蹤有多少數(shù)據(jù)讀入緩沖區(qū)栓霜,并返回真或假,這取決于緩沖區(qū)是否已滿横蜒。換句話說胳蛮,如果緩沖區(qū)準備好被處理,那么表示緩沖區(qū)滿了丛晌。

bufferFull()方法掃描緩沖區(qū)鹰霍,但必須保持在bufferFull()方法被調(diào)用之前狀態(tài)相同。如果沒有茵乱,下一個讀入緩沖區(qū)的數(shù)據(jù)可能無法讀到正確的位置茂洒。這是不可能的,但卻是需要注意的又一問題瓶竭。

如果緩沖區(qū)已滿督勺,它可以被處理。如果它不滿斤贰,并且在你的實際案例中有意義智哀,你或許能處理其中的部分數(shù)據(jù)。但是許多情況下并非如此荧恍。下圖展示了“緩沖區(qū)數(shù)據(jù)循環(huán)就緒”:

從一個通道里讀數(shù)據(jù)瓷叫,直到所有的數(shù)據(jù)都讀到緩沖區(qū)里

總結(jié)

NIO可讓您只使用一個(或幾個)單線程管理多個通道(網(wǎng)絡連接或文件)屯吊,但付出的代價是解析數(shù)據(jù)可能會比從一個阻塞流中讀取數(shù)據(jù)更復雜票从。

如果需要管理同時打開的成千上萬個連接湿痢,這些連接每次只是發(fā)送少量的數(shù)據(jù)循衰,例如聊天服務器舰攒,實現(xiàn)NIO的服務器可能是一個優(yōu)勢兜看。同樣哮伟,如果你需要維持許多打開的連接到其他計算機上亮蒋,如P2P網(wǎng)絡中献幔,使用一個單獨的線程來管理你所有出站連接煮寡,可能是一個優(yōu)勢虹蓄。一個線程多個連接的設(shè)計方案如下圖所示:

單線程管理多個連接

如果你有少量的連接使用非常高的帶寬,一次發(fā)送大量的數(shù)據(jù)幸撕,也許典型的IO服務器實現(xiàn)可能非常契合薇组。下圖說明了一個典型的IO服務器設(shè)計:

一個典型的IO服務器設(shè)計:一個連接通過一個線程處理

通道(Channel)

(本部分原文鏈接,作者:Jakob Jenkov坐儿,譯者:airu体箕,校對:丁一)

Java NIO的通道類似流,但又有些不同:

既可以從通道中讀取數(shù)據(jù)挑童,又可以寫數(shù)據(jù)到通道累铅。但流的讀寫通常是單向的。

通道可以異步地讀寫站叼。

通道中的數(shù)據(jù)總是要先讀到一個Buffer娃兽,或者總是要從一個Buffer中寫入。

正如上面所說尽楔,從通道讀取數(shù)據(jù)到緩沖區(qū)投储,從緩沖區(qū)寫入數(shù)據(jù)到通道。如下圖所示:

Channel的實現(xiàn)

這些是Java NIO中最重要的通道的實現(xiàn):

FileChannel:從文件中讀寫數(shù)據(jù)阔馋。

DatagramChannel:能通過UDP讀寫網(wǎng)絡中的數(shù)據(jù)玛荞。

SocketChannel:能通過TCP讀寫網(wǎng)絡中的數(shù)據(jù)。

ServerSocketChannel:可以監(jiān)聽新進來的TCP連接呕寝,像Web服務器那樣勋眯。對每一個新進來的連接都會創(chuàng)建一個SocketChannel。

基本的 Channel 示例

下面是一個使用FileChannel讀取數(shù)據(jù)到Buffer中的示例:

Java代碼?

RandomAccessFile?aFile?=newRandomAccessFile("data/nio-data.txt","rw");

FileChannel?inChannel?=?aFile.getChannel();??


ByteBuffer?buf?=?ByteBuffer.allocate(48);


intbytesRead?=?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();??

注意 buf.flip() 的調(diào)用下梢,首先讀取數(shù)據(jù)到Buffer客蹋,然后反轉(zhuǎn)Buffer,接著再從Buffer中讀取數(shù)據(jù)。下一節(jié)會深入講解Buffer的更多細節(jié)孽江。

緩沖區(qū)(Buffer)

Java NIO中的Buffer用于和NIO通道進行交互讶坯。如你所知,數(shù)據(jù)是從通道讀入緩沖區(qū)岗屏,從緩沖區(qū)寫入到通道中的辆琅。

緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù)漱办,然后可以從中讀取數(shù)據(jù)的內(nèi)存。這塊內(nèi)存被包裝成NIO Buffer對象婉烟,并提供了一組方法娩井,用來方便的訪問該塊內(nèi)存。

Buffer的基本用法

使用Buffer讀寫數(shù)據(jù)一般遵循以下四個步驟:

寫入數(shù)據(jù)到Buffer

調(diào)用flip()方法

從Buffer中讀取數(shù)據(jù)

調(diào)用clear()方法或者compact()方法

當向buffer寫入數(shù)據(jù)時隅很,buffer會記錄下寫了多少數(shù)據(jù)撞牢。一旦要讀取數(shù)據(jù)率碾,需要通過flip()方法將Buffer從寫模式切換到讀模式叔营。在讀模式下,可以讀取之前寫入到buffer的所有數(shù)據(jù)所宰。

一旦讀完了所有的數(shù)據(jù)绒尊,就需要清空緩沖區(qū),讓它可以再次被寫入仔粥。有兩種方式能清空緩沖區(qū):調(diào)用clear()或compact()方法婴谱。clear()方法會清空整個緩沖區(qū)。compact()方法只會清除已經(jīng)讀過的數(shù)據(jù)躯泰。任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處谭羔,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面。

下面是一個使用Buffer的例子:

Java代碼?

RandomAccessFile?aFile?=newRandomAccessFile("data/nio-data.txt","rw");

FileChannel?inChannel?=?aFile.getChannel();??


//create?buffer?with?capacity?of?48?bytes

ByteBuffer?buf?=?ByteBuffer.allocate(48);


intbytesRead?=?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();??

Buffer的capacity,position和limit

緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù)麦向,然后可以從中讀取數(shù)據(jù)的內(nèi)存瘟裸。這塊內(nèi)存被包裝成NIO Buffer對象,并提供了一組方法诵竭,用來方便的訪問該塊內(nèi)存话告。

為了理解Buffer的工作原理,需要熟悉它的三個屬性:

capacity

position

limit

position和limit的含義取決于Buffer處在讀模式還是寫模式卵慰。不管Buffer處在什么模式沙郭,capacity的含義總是一樣的。

這里有一個關(guān)于capacity裳朋,position和limit在讀寫模式中的說明病线,詳細的解釋在插圖后面。

capacity

作為一個內(nèi)存塊鲤嫡,Buffer有一個固定的大小值氧苍,也叫“capacity”.你只能往里寫capacity個byte、long泛范,char等類型让虐。一旦Buffer滿了,需要將其清空(通過讀數(shù)據(jù)或者清除數(shù)據(jù))才能繼續(xù)寫數(shù)據(jù)往里寫數(shù)據(jù)罢荡。

position

當你寫數(shù)據(jù)到Buffer中時赡突,position表示當前的位置对扶。初始的position值為0.當一個byte、long等數(shù)據(jù)寫到Buffer后惭缰, position會向前移動到下一個可插入數(shù)據(jù)的Buffer單元浪南。position最大可為capacity – 1。

當讀取數(shù)據(jù)時漱受,也是從某個特定位置讀络凿。當將Buffer從寫模式切換到讀模式,position會被重置為0昂羡。當從Buffer的position處讀取數(shù)據(jù)時絮记,position向前移動到下一個可讀的位置。

limit

在寫模式下虐先,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)怨愤。 寫模式下,limit等于Buffer的capacity蛹批。

當切換Buffer到讀模式時撰洗, limit表示你最多能讀到多少數(shù)據(jù)。因此腐芍,當切換Buffer到讀模式時差导,limit會被設(shè)置成寫模式下的position值。換句話說猪勇,你能讀到之前寫入的所有數(shù)據(jù)(limit被設(shè)置成已寫數(shù)據(jù)的數(shù)量设褐,這個值在寫模式下就是position)

Buffer的類型

Java NIO 有以下Buffer類型:

ByteBuffer

MappedByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

如你所見,這些Buffer類型代表了不同的數(shù)據(jù)類型埠对。換句話說络断,就是可以通過char,short项玛,int貌笨,long,float 或 double類型來操作緩沖區(qū)中的字節(jié)襟沮。

MappedByteBuffer 有些特別锥惋,在涉及它的專門章節(jié)中再講。

Buffer的分配

要想獲得一個Buffer對象首先要進行分配开伏。 每一個Buffer類都有一個allocate方法膀跌。下面是一個分配48字節(jié)capacity的ByteBuffer的例子。

Java代碼?

ByteBuffer?buf?=?ByteBuffer.allocate(48);

這是分配一個可存儲1024個字符的CharBuffer:

Java代碼?

CharBuffer?buf?=?CharBuffer.allocate(1024);

向Buffer中寫數(shù)據(jù)

寫數(shù)據(jù)到Buffer有兩種方式:

從Channel寫到Buffer固灵。

通過Buffer的put()方法寫到Buffer里捅伤。

從Channel寫到Buffer的例子

Java代碼?

intbytesRead?=?inChannel.read(buf);//read?into?buffer.

通過put方法寫B(tài)uffer的例子:

Java代碼?

buf.put(127);

put方法有很多版本,允許你以不同的方式把數(shù)據(jù)寫入到Buffer中巫玻。例如丛忆, 寫到一個指定的位置祠汇,或者把一個字節(jié)數(shù)組寫入到Buffer。 更多Buffer實現(xiàn)的細節(jié)參考JavaDoc熄诡。

flip()方法

flip方法將Buffer從寫模式切換到讀模式可很。調(diào)用flip()方法會將position設(shè)回0,并將limit設(shè)置成之前position的值凰浮。

換句話說我抠,position現(xiàn)在用于標記讀的位置,limit表示之前寫進了多少個byte袜茧、char等 —— 現(xiàn)在能讀取多少個byte菜拓、char等。

從Buffer中讀取數(shù)據(jù)

從Buffer中讀取數(shù)據(jù)有兩種方式:

從Buffer讀取數(shù)據(jù)到Channel惫周。

使用get()方法從Buffer中讀取數(shù)據(jù)尘惧。

從Buffer讀取數(shù)據(jù)到Channel的例子:

Java代碼?

//read?from?buffer?into?channel.

intbytesWritten?=?inChannel.write(buf);

使用get()方法從Buffer中讀取數(shù)據(jù)的例子

Java代碼?

byteaByte?=?buf.get();

get方法有很多版本康栈,允許你以不同的方式從Buffer中讀取數(shù)據(jù)递递。例如,從指定position讀取啥么,或者從Buffer中讀取數(shù)據(jù)到字節(jié)數(shù)組登舞。更多Buffer實現(xiàn)的細節(jié)參考JavaDoc。

rewind()方法

Buffer.rewind()將position設(shè)回0悬荣,所以你可以重讀Buffer中的所有數(shù)據(jù)菠秒。limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte氯迂、char等)践叠。

clear()與compact()方法

一旦讀完Buffer中的數(shù)據(jù),需要讓Buffer準備好再次被寫入嚼蚀〗疲可以通過clear()或compact()方法來完成。

如果調(diào)用的是clear()方法轿曙,position將被設(shè)回0弄捕,limit被設(shè)置成 capacity的值。換句話說导帝,Buffer 被清空了守谓。Buffer中的數(shù)據(jù)并未清除,只是這些標記告訴我們可以從哪里開始往Buffer里寫數(shù)據(jù)您单。

如果Buffer中有一些未讀的數(shù)據(jù)斋荞,調(diào)用clear()方法,數(shù)據(jù)將“被遺忘”虐秦,意味著不再有任何標記會告訴你哪些數(shù)據(jù)被讀過平酿,哪些還沒有讯檐。

如果Buffer中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù)染服,但是此時想要先先寫些數(shù)據(jù)别洪,那么使用compact()方法。

compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處柳刮。然后將position設(shè)到最后一個未讀元素正后面挖垛。limit屬性依然像clear()方法一樣,設(shè)置成capacity”牛現(xiàn)在Buffer準備好寫數(shù)據(jù)了痢毒,但是不會覆蓋未讀的數(shù)據(jù)。

mark()與reset()方法

通過調(diào)用Buffer.mark()方法蚕甥,可以標記Buffer中的一個特定position哪替。之后可以通過調(diào)用Buffer.reset()方法恢復到這個position。例如:

Java代碼?

buffer.mark();??


//call?buffer.get()?a?couple?of?times,?e.g.?during?parsing.


buffer.reset();//set?position?back?to?mark.

equals()與compareTo()方法

可以使用equals()和compareTo()方法兩個Buffer菇怀。

equals()

當滿足下列條件時凭舶,表示兩個Buffer相等:

有相同的類型(byte、char爱沟、int等)帅霜。

Buffer中剩余的byte、char等的個數(shù)相等呼伸。

Buffer中所有剩余的byte身冀、char等都相同。

如你所見括享,equals只是比較Buffer的一部分搂根,不是每一個在它里面的元素都比較。實際上铃辖,它只比較Buffer中的剩余元素剩愧。

compareTo()方法

compareTo()方法比較兩個Buffer的剩余元素(byte、char等)澳叉, 如果滿足下列條件隙咸,則認為一個Buffer“小于”另一個Buffer:

第一個不相等的元素小于另一個Buffer中對應的元素。

所有元素都相等成洗,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數(shù)比另一個少)五督。

(譯注:剩余元素是從 position到limit之間的元素)

分散(Scatter)/聚集(Gather)

(本部分原文地址,作者:Jakob Jenkov?? 譯者:郭蕾)

Java NIO開始支持scatter/gather瓶殃,scatter/gather用于描述從Channel(譯者注:Channel在中文經(jīng)常翻譯為通道)中讀取或者寫入到Channel的操作充包。

分散(scatter)從Channel中讀取是指在讀操作時將讀取的數(shù)據(jù)寫入多個buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個Buffer中基矮。

聚集(gather)寫入Channel是指在寫操作時將多個buffer的數(shù)據(jù)寫入同一個Channel淆储,因此,Channel 將多個Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel家浇。

scatter / gather經(jīng)常用于需要將傳輸?shù)臄?shù)據(jù)分開處理的場合本砰,例如傳輸一個由消息頭和消息體組成的消息,你可能會將消息體和消息頭分散到不同的buffer中钢悲,這樣你可以方便的處理消息頭和消息體点额。

Scattering Reads

Scattering Reads是指數(shù)據(jù)從一個channel讀取到多個buffer中。如下圖描述:

代碼示例如下:

Java代碼?

ByteBuffer?header?=?ByteBuffer.allocate(128);

ByteBuffer?body???=?ByteBuffer.allocate(1024);


ByteBuffer[]?bufferArray?=?{?header,?body?};??


channel.read(bufferArray);??

注意buffer首先被插入到數(shù)組莺琳,然后再將數(shù)組作為channel.read() 的輸入?yún)?shù)还棱。read()方法按照buffer在數(shù)組中的順序?qū)腸hannel中讀取的數(shù)據(jù)寫入到buffer,當一個buffer被寫滿后惭等,channel緊接著向另一個buffer中寫珍手。

Scattering Reads在移動下一個buffer前,必須填滿當前的buffer辞做,這也意味著它不適用于動態(tài)消息(譯者注:消息大小不固定)琳要。換句話說,如果存在消息頭和消息體凭豪,消息頭必須完成填充(例如 128byte)焙蹭,Scattering Reads才能正常工作晒杈。

Gathering Writes

Gathering Writes是指數(shù)據(jù)從多個buffer寫入到同一個channel嫂伞。如下圖描述:

代碼示例如下:

Java代碼?

ByteBuffer?header?=?ByteBuffer.allocate(128);

ByteBuffer?body???=?ByteBuffer.allocate(1024);


//write?data?into?buffers


ByteBuffer[]?bufferArray?=?{?header,?body?};??


channel.write(bufferArray);??

buffers數(shù)組是write()方法的入?yún)ⅲ瑆rite()方法會按照buffer在數(shù)組中的順序拯钻,將數(shù)據(jù)寫入到channel帖努,注意只有position和limit之間的數(shù)據(jù)才會被寫入。因此粪般,如果一個buffer的容量為128byte拼余,但是僅僅包含58byte的數(shù)據(jù),那么這58byte的數(shù)據(jù)將被寫入到channel中亩歹。因此與Scattering Reads相反匙监,Gathering Writes能較好的處理動態(tài)消息。

通道之間的數(shù)據(jù)傳輸

(本部分原文地址小作,作者:Jakob Jenkov亭姥,譯者:郭蕾,校對:周泰)

在Java NIO中顾稀,如果兩個通道中有一個是FileChannel达罗,那你可以直接將數(shù)據(jù)從一個channel(譯者注:channel中文常譯作通道)傳輸?shù)搅硗庖粋€channel。

transferFrom()

FileChannel的transferFrom()方法可以將數(shù)據(jù)從源通道傳輸?shù)紽ileChannel中(譯者注:這個方法在JDK文檔中的解釋為將字節(jié)從給定的可讀取字節(jié)通道傳輸?shù)酱送ǖ赖奈募校┚哺选O旅媸且粋€簡單的例子:

Java代碼?

RandomAccessFile?fromFile?=newRandomAccessFile("fromFile.txt","rw");

FileChannel??????fromChannel?=?fromFile.getChannel();??


RandomAccessFile?toFile?=newRandomAccessFile("toFile.txt","rw");

FileChannel??????toChannel?=?toFile.getChannel();??


longposition?=0;

longcount?=?fromChannel.size();


toChannel.transferFrom(position,?count,?fromChannel);??

方法的輸入?yún)?shù)position表示從position處開始向目標文件寫入數(shù)據(jù)粮揉,count表示最多傳輸?shù)淖止?jié)數(shù)巡李。如果源通道的剩余空間小于 count 個字節(jié),則所傳輸?shù)淖止?jié)數(shù)要小于請求的字節(jié)數(shù)扶认。

此外要注意侨拦,在SoketChannel的實現(xiàn)中,SocketChannel只會傳輸此刻準備好的數(shù)據(jù)(可能不足count字節(jié))辐宾。因此阳谍,SocketChannel可能不會將請求的所有數(shù)據(jù)(count個字節(jié))全部傳輸?shù)紽ileChannel中。

transferTo()

transferTo()方法將數(shù)據(jù)從FileChannel傳輸?shù)狡渌腸hannel中螃概。下面是一個簡單的例子:

Java代碼?

RandomAccessFile?fromFile?=newRandomAccessFile("fromFile.txt","rw");

FileChannel??????fromChannel?=?fromFile.getChannel();??


RandomAccessFile?toFile?=newRandomAccessFile("toFile.txt","rw");

FileChannel??????toChannel?=?toFile.getChannel();??


longposition?=0;

longcount?=?fromChannel.size();


fromChannel.transferTo(position,?count,?toChannel);??

是不是發(fā)現(xiàn)這個例子和前面那個例子特別相似矫夯?除了調(diào)用方法的FileChannel對象不一樣外,其他的都一樣吊洼。

上面所說的關(guān)于SocketChannel的問題在transferTo()方法中同樣存在训貌。SocketChannel會一直傳輸數(shù)據(jù)直到目標buffer被填滿。

選擇器(Selector)

(本部分原文鏈接冒窍,作者:Jakob Jenkov递沪,譯者:浪跡v,校對:丁一)

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道综液,并能夠知曉通道是否為諸如讀寫事件做好準備的組件款慨。這樣,一個單獨的線程可以管理多個channel谬莹,從而管理多個網(wǎng)絡連接檩奠。

(1)? 為什么使用Selector?

僅用單個線程來處理多個Channels的好處是,只需要更少的線程來處理通道附帽。事實上埠戳,可以只用一個線程處理所有的通道。對于操作系統(tǒng)來說蕉扮,線程之間上下文切換的開銷很大整胃,而且每個線程都要占用系統(tǒng)的一些資源(如內(nèi)存)。因此喳钟,使用的線程越少越好屁使。

但是,需要記住奔则,現(xiàn)代的操作系統(tǒng)和CPU在多任務方面表現(xiàn)的越來越好蛮寂,所以多線程的開銷隨著時間的推移,變得越來越小了应狱。實際上共郭,如果一個CPU有多個內(nèi)核,不使用多任務可能是在浪費CPU能力。不管怎么說除嘹,關(guān)于那種設(shè)計的討論應該放在另一篇不同的文章中写半。在這里,只要知道使用Selector能夠處理多個通道就足夠了尉咕。

下面是單線程使用一個Selector處理3個channel的示例圖:

(2)? Selector的創(chuàng)建

通過調(diào)用Selector.open()方法創(chuàng)建一個Selector叠蝇,如下:

Java代碼?

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

(3) 向Selector注冊通道

為了將Channel和Selector配合使用,必須將channel注冊到selector上年缎。通過SelectableChannel.register()方法來實現(xiàn)悔捶,如下:

Java代碼?

channel.configureBlocking(false);

SelectionKey?key?=?channel.register(selector,??

????Selectionkey.OP_READ);??

與Selector一起使用時,Channel必須處于非阻塞模式下单芜。這意味著不能將FileChannel與Selector一起使用蜕该,因為FileChannel不能切換到非阻塞模式。而套接字通道都可以洲鸠。

注意register()方法的第二個參數(shù)堂淡。這是一個“interest集合”,意思是在通過Selector監(jiān)聽Channel時對什么事件感興趣扒腕【畹恚可以監(jiān)聽四種不同類型的事件:

Connect

Accept

Read

Write

通道觸發(fā)了一個事件意思是該事件已經(jīng)就緒。所以瘾腰,某個channel成功連接到另一個服務器稱為“連接就緒”皆的。一個server socket channel準備好接收新進入的連接稱為“接收就緒”。一個有數(shù)據(jù)可讀的通道可以說是“讀就緒”蹋盆。等待寫數(shù)據(jù)的通道可以說是“寫就緒”费薄。

這四種事件用SelectionKey的四個常量來表示:

SelectionKey.OP_CONNECT

SelectionKey.OP_ACCEPT

SelectionKey.OP_READ

SelectionKey.OP_WRITE

如果你對不止一種事件感興趣,那么可以用“位或”操作符將常量連接起來怪嫌,如下:

Java代碼?

intinterestSet?=?SelectionKey.OP_READ?|?SelectionKey.OP_WRITE;

在下面還會繼續(xù)提到interest集合义锥。

(4)? SelectionKey

在上一小節(jié)中,當向Selector注冊Channel時岩灭,register()方法會返回一個SelectionKey對象。這個對象包含了一些你感興趣的屬性:

interest集合

ready集合

Channel

Selector

附加的對象(可選)

下面我會描述這些屬性赂鲤。

interest集合

就像向Selector注冊通道一節(jié)中所描述的噪径,interest集合是你所選擇的感興趣的事件集合∈酰可以通過SelectionKey讀寫interest集合找爱,像這樣:

Java代碼?

intinterestSet?=?selectionKey.interestOps();


booleanisInterestedInAccept??=?(interestSet?&?SelectionKey.OP_ACCEPT)?==?SelectionKey.OP_ACCEPT;

booleanisInterestedInConnect?=?interestSet?&?SelectionKey.OP_CONNECT;

booleanisInterestedInRead????=?interestSet?&?SelectionKey.OP_READ;

booleanisInterestedInWrite???=?interestSet?&?SelectionKey.OP_WRITE;

可以看到泡孩,用“位與”操作interest 集合和給定的SelectionKey常量车摄,可以確定某個確定的事件是否在interest 集合中。

ready集合

ready 集合是通道已經(jīng)準備就緒的操作的集合。在一次選擇(Selection)之后吮播,你會首先訪問這個ready set变屁。Selection將在下一小節(jié)進行解釋∫夂荩可以這樣訪問ready集合:

int readySet = selectionKey.readyOps();

可以用像檢測interest集合那樣的方法粟关,來檢測channel中什么事件或操作已經(jīng)就緒。但是环戈,也可以使用以下四個方法闷板,它們都會返回一個布爾類型:

Java代碼?

selectionKey.isAcceptable();??

selectionKey.isConnectable();??

selectionKey.isReadable();??

selectionKey.isWritable();??

Channel + Selector

從SelectionKey訪問Channel和Selector很簡單。如下:

Java代碼?

Channel??channel??=?selectionKey.channel();??

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

附加的對象

可以將一個對象或者更多信息附著到SelectionKey上院塞,這樣就能方便的識別某個給定的通道遮晚。例如,可以附加 與通道一起使用的Buffer拦止,或是包含聚集數(shù)據(jù)的某個對象鹏漆。使用方法如下:

Java代碼?

selectionKey.attach(theObject);??

Object?attachedObj?=?selectionKey.attachment();??

還可以在用register()方法向Selector注冊Channel的時候附加對象。如:

Java代碼?

SelectionKey?key?=?channel.register(selector,?SelectionKey.OP_READ,?theObject);??

(5)? 通過Selector選擇通道

一旦向Selector注冊了一或多個通道创泄,就可以調(diào)用幾個重載的select()方法艺玲。這些方法返回你所感興趣的事件(如連接、接受鞠抑、讀或?qū)懀┮呀?jīng)準備就緒的那些通道饭聚。換句話說,如果你對“讀就緒”的通道感興趣搁拙,select()方法會返回讀事件已經(jīng)就緒的那些通道秒梳。

下面是select()方法:

int select()

int select(long timeout)

int selectNow()

select()阻塞到至少有一個通道在你注冊的事件上就緒了。

select(long timeout)和select()一樣箕速,除了最長會阻塞timeout毫秒(參數(shù))青责。

selectNow()不會阻塞,不管什么通道就緒都立刻返回(譯者注:此方法執(zhí)行非阻塞的選擇操作棍苹。如果自從前一次選擇操作后肤视,沒有通道變成可選擇的,則此方法直接返回零字柠。)探越。

select()方法返回的int值表示有多少通道已經(jīng)就緒。亦即窑业,自上次調(diào)用select()方法后有多少通道變成就緒狀態(tài)钦幔。如果調(diào)用select()方法,因為有一個通道變成就緒狀態(tài)常柄,返回了1鲤氢,若再次調(diào)用select()方法搀擂,如果另一個通道就緒了,它會再次返回1卷玉。如果對第一個就緒的channel沒有做任何操作哨颂,現(xiàn)在就有兩個就緒的通道,但在每次select()方法調(diào)用之間揍庄,只有一個通道就緒了咆蒿。

selectedKeys()

一旦調(diào)用了select()方法,并且返回值表明有一個或更多個通道就緒了蚂子,然后可以通過調(diào)用selector的selectedKeys()方法沃测,訪問“已選擇鍵集(selected key set)”中的就緒通道。如下所示:

Java代碼?

Set?selectedKeys?=?selector.selectedKeys();??

當像Selector注冊Channel時食茎,Channel.register()方法會返回一個SelectionKey 對象蒂破。這個對象代表了注冊到該Selector的通道”鹩妫可以通過SelectionKey的selectedKeySet()方法訪問這些對象附迷。

可以遍歷這個已選擇的鍵集合來訪問就緒的通道。如下:

Java代碼?

Set?selectedKeys?=?selector.selectedKeys();??

Iterator?keyIterator?=?selectedKeys.iterator();??

while(keyIterator.hasNext())?{

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

if(key.isAcceptable())?{

//?a?connection?was?accepted?by?a?ServerSocketChannel.

}elseif(key.isConnectable())?{

//?a?connection?was?established?with?a?remote?server.

}elseif(key.isReadable())?{

//?a?channel?is?ready?for?reading

}elseif(key.isWritable())?{

//?a?channel?is?ready?for?writing

????}??

keyIterator.remove();

}??

這個循環(huán)遍歷已選擇鍵集中的每個鍵哎媚,并檢測各個鍵所對應的通道的就緒事件喇伯。

注意每次迭代末尾的keyIterator.remove()調(diào)用。Selector不會自己從已選擇鍵集中移除SelectionKey實例拨与。必須在處理完通道時自己移除稻据。下次該通道變成就緒時,Selector會再次將其放入已選擇鍵集中买喧。

SelectionKey.channel()方法返回的通道需要轉(zhuǎn)型成你要處理的類型捻悯,如ServerSocketChannel或SocketChannel等。

(6)? wakeUp()

某個線程調(diào)用select()方法后阻塞了淤毛,即使沒有通道已經(jīng)就緒今缚,也有辦法讓其從select()方法返回。只要讓其它線程在第一個線程調(diào)用select()方法的那個對象上調(diào)用Selector.wakeup()方法即可低淡。阻塞在select()方法上的線程會立馬返回姓言。

如果有其它線程調(diào)用了wakeup()方法,但當前沒有線程阻塞在select()方法上查牌,下個調(diào)用select()方法的線程會立即“醒來(wake up)”事期。

(7)? close()

用完Selector后調(diào)用其close()方法會關(guān)閉該Selector,且使注冊到該Selector上的所有SelectionKey實例無效纸颜。通道本身并不會關(guān)閉。

(8)? 完整的示例

這里有一個完整的示例绎橘,打開一個Selector胁孙,注冊一個通道注冊到這個Selector上(通道的初始化過程略去),然后持續(xù)監(jiān)控這個Selector的四種事件(接受唠倦,連接,讀涮较,寫)是否就緒稠鼻。

Java代碼?

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

channel.configureBlocking(false);

SelectionKey?key?=?channel.register(selector,?SelectionKey.OP_READ);??

while(true)?{

intreadyChannels?=?selector.select();

if(readyChannels?==0)continue;

??Set?selectedKeys?=?selector.selectedKeys();??

??Iterator?keyIterator?=?selectedKeys.iterator();??

while(keyIterator.hasNext())?{

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

if(key.isAcceptable())?{

//?a?connection?was?accepted?by?a?ServerSocketChannel.

}elseif(key.isConnectable())?{

//?a?connection?was?established?with?a?remote?server.

}elseif(key.isReadable())?{

//?a?channel?is?ready?for?reading

}elseif(key.isWritable())?{

//?a?channel?is?ready?for?writing

????}??

keyIterator.remove();

??}??

}??

文件通道

(本部分原文鏈接,作者:Jakob Jenkov狂票,譯者:周泰候齿,校對:丁一)

Java NIO中的FileChannel是一個連接到文件的通道」胧簦可以通過文件通道讀寫文件慌盯。

FileChannel無法設(shè)置為非阻塞模式,它總是運行在阻塞模式下掂器。

打開FileChannel

在使用FileChannel之前亚皂,必須先打開它。但是国瓮,我們無法直接打開一個FileChannel灭必,需要通過使用一個InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例乃摹。下面是通過RandomAccessFile打開FileChannel的示例:

Java代碼?

RandomAccessFile?aFile?=newRandomAccessFile("data/nio-data.txt","rw");

FileChannel?inChannel?=?aFile.getChannel();??

從FileChannel讀取數(shù)據(jù)

調(diào)用多個read()方法之一從FileChannel中讀取數(shù)據(jù)禁漓。如:

Java代碼?

ByteBuffer?buf?=?ByteBuffer.allocate(48);

intbytesRead?=?inChannel.read(buf);

首先,分配一個Buffer孵睬。從FileChannel中讀取的數(shù)據(jù)將被讀到Buffer中播歼。

然后,調(diào)用FileChannel.read()方法肪康。該方法將數(shù)據(jù)從FileChannel讀取到Buffer中荚恶。read()方法返回的int值表示了有多少字節(jié)被讀到了Buffer中。如果返回-1磷支,表示到了文件末尾谒撼。

向FileChannel寫數(shù)據(jù)

使用FileChannel.write()方法向FileChannel寫數(shù)據(jù),該方法的參數(shù)是一個Buffer雾狈。如:

Java代碼?

String?newData?="New?String?to?write?to?file..."+?System.currentTimeMillis();


ByteBuffer?buf?=?ByteBuffer.allocate(48);

buf.clear();??

buf.put(newData.getBytes());??


buf.flip();??


while(buf.hasRemaining())?{

????channel.write(buf);??

}??

注意FileChannel.write()是在while循環(huán)中調(diào)用的廓潜。因為無法保證write()方法一次能向FileChannel寫入多少字節(jié),因此需要重復調(diào)用write()方法善榛,直到Buffer中已經(jīng)沒有尚未寫入通道的字節(jié)辩蛋。

關(guān)閉FileChannel

用完FileChannel后必須將其關(guān)閉。如:

Java代碼?

channel.close();??

FileChannel的position方法

有時可能需要在FileChannel的某個特定位置進行數(shù)據(jù)的讀/寫操作移盆〉吭海可以通過調(diào)用position()方法獲取FileChannel的當前位置。

也可以通過調(diào)用position(long pos)方法設(shè)置FileChannel的當前位置咒循。

這里有兩個例子:

Java代碼?

longpos?=?channel.position();

channel.position(pos?+123);

如果將位置設(shè)置在文件結(jié)束符之后据途,然后試圖從文件通道中讀取數(shù)據(jù)绞愚,讀方法將返回-1 —— 文件結(jié)束標志。

如果將位置設(shè)置在文件結(jié)束符之后颖医,然后向通道中寫數(shù)據(jù)位衩,文件將撐大到當前位置并寫入數(shù)據(jù)。這可能導致“文件空洞”熔萧,磁盤上物理文件中寫入的數(shù)據(jù)間有空隙糖驴。

FileChannel的size方法

FileChannel實例的size()方法將返回該實例所關(guān)聯(lián)文件的大小。如:

Java代碼?

longfileSize?=?channel.size();

FileChannel的truncate方法

可以使用FileChannel.truncate()方法截取一個文件佛致。截取文件時贮缕,文件將中指定長度后面的部分將被刪除。如:

Java代碼?

channel.truncate(1024);

這個例子截取文件的前1024個字節(jié)晌杰。

FileChannel的force方法

FileChannel.force()方法將通道里尚未寫入磁盤的數(shù)據(jù)強制寫到磁盤上跷睦。出于性能方面的考慮,操作系統(tǒng)會將數(shù)據(jù)緩存在內(nèi)存中肋演,所以無法保證寫入到FileChannel里的數(shù)據(jù)一定會即時寫到磁盤上抑诸。要保證這一點,需要調(diào)用force()方法爹殊。

force()方法有一個boolean類型的參數(shù)蜕乡,指明是否同時將文件元數(shù)據(jù)(權(quán)限信息等)寫到磁盤上。

下面的例子同時將文件數(shù)據(jù)和元數(shù)據(jù)強制寫到磁盤上:

Java代碼?

channel.force(true);

Socket 通道

(本部分原文鏈接梗夸,作者:Jakob Jenkov层玲,譯者:鄭玉婷,校對:丁一)

Java NIO中的SocketChannel是一個連接到TCP網(wǎng)絡套接字的通道反症⌒量椋可以通過以下2種方式創(chuàng)建SocketChannel:

打開一個SocketChannel并連接到互聯(lián)網(wǎng)上的某臺服務器。

一個新連接到達ServerSocketChannel時铅碍,會創(chuàng)建一個SocketChannel润绵。

打開 SocketChannel

下面是SocketChannel的打開方式:

Java代碼?

SocketChannel?socketChannel?=?SocketChannel.open();??

socketChannel.connect(newInetSocketAddress("http://jenkov.com",80));

關(guān)閉 SocketChannel

當用完SocketChannel之后調(diào)用SocketChannel.close()關(guān)閉SocketChannel:

Java代碼?

socketChannel.close();??

從 SocketChannel 讀取數(shù)據(jù)

要從SocketChannel中讀取數(shù)據(jù),調(diào)用一個read()的方法之一胞谈。以下是例子:

Java代碼?

ByteBuffer?buf?=?ByteBuffer.allocate(48);

intbytesRead?=?socketChannel.read(buf);

首先尘盼,分配一個Buffer。從SocketChannel讀取到的數(shù)據(jù)將會放到這個Buffer中烦绳。

然后卿捎,調(diào)用SocketChannel.read()。該方法將數(shù)據(jù)從SocketChannel 讀到Buffer中径密。read()方法返回的int值表示讀了多少字節(jié)進Buffer里午阵。如果返回的是-1,表示已經(jīng)讀到了流的末尾(連接關(guān)閉了)享扔。

寫入 SocketChannel

寫數(shù)據(jù)到SocketChannel用的是SocketChannel.write()方法趟庄,該方法以一個Buffer作為參數(shù)括细。示例如下:

Java代碼?

String?newData?="New?String?to?write?to?file..."+?System.currentTimeMillis();


ByteBuffer?buf?=?ByteBuffer.allocate(48);

buf.clear();??

buf.put(newData.getBytes());??


buf.flip();??


while(buf.hasRemaining())?{

????channel.write(buf);??

}??

注意SocketChannel.write()方法的調(diào)用是在一個while循環(huán)中的伪很。Write()方法無法保證能寫多少字節(jié)到SocketChannel戚啥。所以,我們重復調(diào)用write()直到Buffer沒有要寫的字節(jié)為止锉试。

非阻塞模式

可以設(shè)置 SocketChannel 為非阻塞模式(non-blocking mode).設(shè)置之后猫十,就可以在異步模式下調(diào)用connect(), read() 和write()了。

connect()

如果SocketChannel在非阻塞模式下呆盖,此時調(diào)用connect()拖云,該方法可能在連接建立之前就返回了。為了確定連接是否建立应又,可以調(diào)用finishConnect()的方法宙项。像這樣:

Java代碼?

socketChannel.configureBlocking(false);

socketChannel.connect(newInetSocketAddress("http://jenkov.com",80));


while(!?socketChannel.finishConnect()?){

//wait,?or?do?something?else...

}??

write()

非阻塞模式下,write()方法在尚未寫出任何內(nèi)容時可能就返回了株扛。所以需要在循環(huán)中調(diào)用write()尤筐。前面已經(jīng)有例子了,這里就不贅述了洞就。

read()

非阻塞模式下,read()方法在尚未讀取到任何數(shù)據(jù)時可能就返回了盆繁。所以需要關(guān)注它的int返回值,它會告訴你讀取了多少字節(jié)旬蟋。

非阻塞模式與選擇器

非阻塞模式與選擇器搭配會工作的更好油昂,通過將一或多個SocketChannel注冊到Selector,可以詢問選擇器哪個通道已經(jīng)準備好了讀取倾贰,寫入等冕碟。Selector與SocketChannel的搭配使用會在后面詳講。

ServerSocket 通道

(本部分原文鏈接匆浙,作者:Jakob Jenkov安寺,譯者:鄭玉婷,校對:丁一)

Java NIO中的 ServerSocketChannel 是一個可以監(jiān)聽新進來的TCP連接的通道吞彤,就像標準IO中的ServerSocket一樣我衬。ServerSocketChannel類在 java.nio.channels包中。

這里有個例子:

Java代碼?

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


serverSocketChannel.socket().bind(newInetSocketAddress(9999));


while(true){

????SocketChannel?socketChannel?=??

????????????serverSocketChannel.accept();??


//do?something?with?socketChannel...

}??

打開 ServerSocketChannel

通過調(diào)用 ServerSocketChannel.open() 方法來打開ServerSocketChannel.如:

Java代碼?

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

關(guān)閉 ServerSocketChannel

通過調(diào)用ServerSocketChannel.close() 方法來關(guān)閉ServerSocketChannel. 如:

Java代碼?

serverSocketChannel.close();??

監(jiān)聽新進來的連接

通過 ServerSocketChannel.accept() 方法監(jiān)聽新進來的連接饰恕。當 accept()方法返回的時候挠羔,它返回一個包含新進來的連接的 SocketChannel。因此埋嵌,accept()方法會一直阻塞到有新連接到達破加。

通常不會僅僅只監(jiān)聽一個連接,在while循環(huán)中調(diào)用 accept()方法. 如下面的例子:

Java代碼?

while(true){

????SocketChannel?socketChannel?=??

????????????serverSocketChannel.accept();??


//do?something?with?socketChannel...

}??

當然雹嗦,也可以在while循環(huán)中使用除了true以外的其它退出準則范舀。

非阻塞模式

ServerSocketChannel可以設(shè)置成非阻塞模式合是。在非阻塞模式下,accept() 方法會立刻返回锭环,如果還沒有新進來的連接聪全,返回的將是null。 因此辅辩,需要檢查返回的SocketChannel是否是null难礼。如:

Java代碼?

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


serverSocketChannel.socket().bind(newInetSocketAddress(9999));

serverSocketChannel.configureBlocking(false);


while(true){

????SocketChannel?socketChannel?=??

????????????serverSocketChannel.accept();??


if(socketChannel?!=null){

//do?something?with?socketChannel...

????}??

}??

Datagram 通道

(本部分原文鏈接,作者:Jakob Jenkov玫锋,譯者:鄭玉婷蛾茉,校對:丁一)

Java NIO中的DatagramChannel是一個能收發(fā)UDP包的通道。因為UDP是無連接的網(wǎng)絡協(xié)議撩鹿,所以不能像其它通道那樣讀取和寫入谦炬。它發(fā)送和接收的是數(shù)據(jù)包。

打開 DatagramChannel

下面是 DatagramChannel 的打開方式:

Java代碼?

DatagramChannel?channel?=?DatagramChannel.open();??

channel.socket().bind(newInetSocketAddress(9999));

這個例子打開的 DatagramChannel可以在UDP端口9999上接收數(shù)據(jù)包节沦。

接收數(shù)據(jù)

通過receive()方法從DatagramChannel接收數(shù)據(jù)键思,如:

Java代碼?

ByteBuffer?buf?=?ByteBuffer.allocate(48);

buf.clear();??

channel.receive(buf);??

receive()方法會將接收到的數(shù)據(jù)包內(nèi)容復制到指定的Buffer. 如果Buffer容不下收到的數(shù)據(jù),多出的數(shù)據(jù)將被丟棄散劫。

發(fā)送數(shù)據(jù)

通過send()方法從DatagramChannel發(fā)送數(shù)據(jù)稚机,如:

Java代碼?

String?newData?="New?String?to?write?to?file..."+?System.currentTimeMillis();


ByteBuffer?buf?=?ByteBuffer.allocate(48);

buf.clear();??

buf.put(newData.getBytes());??

buf.flip();??


intbytesSent?=?channel.send(buf,newInetSocketAddress("jenkov.com",80));

這個例子發(fā)送一串字符到”jenkov.com”服務器的UDP端口80。 因為服務端并沒有監(jiān)控這個端口获搏,所以什么也不會發(fā)生赖条。也不會通知你發(fā)出的數(shù)據(jù)包是否已收到,因為UDP在數(shù)據(jù)傳送方面沒有任何保證常熙。

連接到特定的地址

可以將DatagramChannel“連接”到網(wǎng)絡中的特定地址的纬乍。由于UDP是無連接的,連接到特定地址并不會像TCP通道那樣創(chuàng)建一個真正的連接裸卫。而是鎖住DatagramChannel 仿贬,讓其只能從特定地址收發(fā)數(shù)據(jù)。

這里有個例子:

Java代碼?

channel.connect(newInetSocketAddress("jenkov.com",80));

當連接后墓贿,也可以使用read()和write()方法茧泪,就像在用傳統(tǒng)的通道一樣。只是在數(shù)據(jù)傳送方面沒有任何保證聋袋。這里有幾個例子:

Java代碼?

intbytesRead?=?channel.read(buf);

intbytesWritten?=?channel.write(but);

管道(Pipe)

(本部分原文鏈接队伟,作者:Jakob Jenkov,譯者:黃忠幽勒,校對:丁一)

Java NIO 管道是2個線程之間的單向數(shù)據(jù)連接嗜侮。Pipe有一個source通道和一個sink通道。數(shù)據(jù)會被寫到sink通道,從source通道讀取锈颗。

這里是Pipe原理的圖示:

創(chuàng)建管道

通過Pipe.open()方法打開管道顷霹。例如:

Java代碼?

Pipe?pipe?=?Pipe.open();??

向管道寫數(shù)據(jù)

要向管道寫數(shù)據(jù),需要訪問sink通道击吱。像這樣:

Java代碼?

Pipe.SinkChannel?sinkChannel?=?pipe.sink();??

通過調(diào)用SinkChannel的write()方法淋淀,將數(shù)據(jù)寫入SinkChannel,像這樣:

Java代碼?

String?newData?="New?String?to?write?to?file..."+?System.currentTimeMillis();

ByteBuffer?buf?=?ByteBuffer.allocate(48);

buf.clear();??

buf.put(newData.getBytes());??


buf.flip();??


while(buf.hasRemaining())?{

????<b>sinkChannel.write(buf);</b>??

}??

從管道讀取數(shù)據(jù)

從讀取管道的數(shù)據(jù),需要訪問source通道姨拥,像這樣:

Java代碼?

Pipe.SourceChannel?sourceChannel?=?pipe.source();??

調(diào)用source通道的read()方法來讀取數(shù)據(jù)绅喉,像這樣:

Java代碼?

ByteBuffer?buf?=?ByteBuffer.allocate(48);


intbytesRead?=?inChannel.read(buf);

read()方法返回的int值會告訴我們多少字節(jié)被讀進了緩沖區(qū)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叫乌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子徽缚,更是在濱河造成了極大的恐慌憨奸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凿试,死亡現(xiàn)場離奇詭異排宰,居然都是意外死亡,警方通過查閱死者的電腦和手機那婉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門板甘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人详炬,你說我怎么就攤上這事盐类。” “怎么了呛谜?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵在跳,是天一觀的道長。 經(jīng)常有香客問我隐岛,道長猫妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任聚凹,我火速辦了婚禮割坠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妒牙。我一直安慰自己彼哼,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布单旁。 她就那樣靜靜地躺著沪羔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔫饰,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天琅豆,我揣著相機與錄音,去河邊找鬼篓吁。 笑死茫因,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的杖剪。 我是一名探鬼主播冻押,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盛嘿!你這毒婦竟也來了洛巢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤次兆,失蹤者是張志新(化名)和其女友劉穎稿茉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芥炭,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡漓库,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了园蝠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渺蒿。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖彪薛,靈堂內(nèi)的尸體忽然破棺而出茂装,到底是詐尸還是另有隱情,我是刑警寧澤陪汽,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布训唱,位于F島的核電站,受9級特大地震影響挚冤,放射性物質(zhì)發(fā)生泄漏况增。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一训挡、第九天 我趴在偏房一處隱蔽的房頂上張望澳骤。 院中可真熱鬧,春花似錦澜薄、人聲如沸为肮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颊艳。三九已至茅特,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棋枕,已是汗流浹背白修。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留重斑,地道東北人兵睛。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像窥浪,于是被迫代替她去往敵國和親祖很。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345