IO 和 NIO的區(qū)別,NIO優(yōu)點

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ù)通道。

阻塞IO和非阻塞IO

Java IO流都是阻塞的,這意味著,當一條線程執(zhí)行read()或者write()方法時,這條線程會一直阻塞直到讀取到了一些數(shù)據(jù)或者要寫出去的數(shù)據(jù)已經全部寫出,在這期間這條線程不能做任何其他的事情。

java NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式,阻塞模式的NIO除了使用Buffer存儲數(shù)據(jù)外和IO基本沒有區(qū)別)允許一條線程從channel中讀取數(shù)據(jù),通過返回值來判斷buffer中是否有數(shù)據(jù),如果沒有數(shù)據(jù),NIO不會阻塞,因為不阻塞這條線程就可以去做其他的事情,過一段時間再回來判斷一下有沒有數(shù)據(jù)。NIO的寫也是一樣的,一條線程將buffer中的數(shù)據(jù)寫入channel,它不會等待數(shù)據(jù)全部寫完才會返回,而是調用完write()方法就會繼續(xù)向下執(zhí)行

選擇器(Selectors)

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

面向流與面向緩沖

Java IO和NIO之間第一個最大的區(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ù)。

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

Channels/Buffers/Selectors

?數(shù)據(jù)可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。

Channel

Channel的實現(xiàn):(涵蓋了UDP和TCP網絡IO以及文件IO)

FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel

讀數(shù)據(jù):int bytesRead = inChannel.read(buf);

寫數(shù)據(jù):int bytesWritten = inChannel.write(buf); ?

還有部分的使用,如配置Channel為阻塞或者非阻塞模式,以及如何注冊到Selector上面去,參考Selector部分;

Buffer

Buffer實現(xiàn):(byte,char,short,int,long,float,double)

ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer

Buffer使用:

讀數(shù)據(jù):

flip()方法:將Buffer從寫模式切換到讀模式;調用flip()方法會將position設回0,并將limit設置成之前position的值。

(char) buf.get():讀取數(shù)據(jù)

rewind():將position設回0,所以你可以重讀Buffer中的所有數(shù)據(jù);limit保持不變,仍然表示能從Buffer中讀取多少個元素(byte、char等)。

mark()方法:可以標記Buffer中的一個特定position。

reset()方法:恢復到Buffer.mark()標記時的position。

一旦讀完了所有的數(shù)據(jù),就需要清空緩沖區(qū),讓它可以再次被寫入。

clear()方法:清空整個緩沖區(qū);position將被設回0,limit被設置成 capacity的值。

compact()方法:只會清除已經讀過的數(shù)據(jù);任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處,新寫入的數(shù)據(jù)將放到緩沖區(qū)未讀數(shù)據(jù)的后面;將position設到最后一個未讀元素正后面,limit被設置成 capacity的值。

寫數(shù)據(jù)

buf.put(127); ?

Buffer的三個屬性:

capacity:含義與模式無關;Buffer的一個固定的大小值;Buffer滿了需要將其清空才能再寫;

ByteBuffer.allocate(48);該buffer的capacity為48byte

CharBuffer.allocate(1024);該buffer的capacity為1024個char?

position:含義取決于Buffer處在讀模式還是寫模式(初始值為0,寫或者讀操作的當前位置)

寫數(shù)據(jù)時,初始的position值為0;其值最大可為capacity-1

將Buffer從寫模式切換到讀模式,position會被重置為0

limit:含義取決于Buffer處在讀模式還是寫模式(寫limit=capacity;讀limit等于最多可以讀取到的數(shù)據(jù))

寫模式下,limit等于Buffer的capacity

切換Buffer到讀模式時, limit表示你最多能讀到多少數(shù)據(jù);

Selector

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

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

創(chuàng)建:

Selector selector = Selector.open(); ?

注冊通道:

channel.configureBlocking(false); ?

/*與Selector一起使用時,Channel必須處于非阻塞模式,這意味著不能將FileChannel與Selector一起使用,因為FileChannel不能切換到非阻塞模式(而套接字通道都可以)*/

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

/*第二個參數(shù)表明Selector監(jiān)聽Channel時對什么事件感興趣(SelectionKey.OP_CONNECT ?SelectionKey.OP_ACCEPT ?SelectionKey.OP_READ SelectionKey.OP_WRITE),可以用或操作符將多個興趣組合一起*/

SelectionKey

包含了interest集合 、ready集合 、Channel 、Selector 、附加的對象(可選)

int interestSet = key.interestOps();可以進行類似interestSet & SelectionKey.OP_CONNECT的判斷

使用:

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

selectNow():不會阻塞,不管什么通道就緒都立刻返回

selectedKeys():訪問“已選擇鍵集(selected key set)”中的就緒通道

close():使用完selector需要用其close()方法會關閉該Selector,且使注冊到該Selector上的所有SelectionKey實例無效

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.?????

?????}?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();//注意這里必須手動remove;表明該selectkey我已經處理過了;

}



Java測試關鍵代碼

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

FileChannel?inChannel?=?aFile.getChannel(); ?//從一個InputStream outputstream中獲取channel

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

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

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

文件通道

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

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

讀數(shù)據(jù)

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

int?bytesRead?=?inChannel.read(buf);

寫數(shù)據(jù)

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);??

}?

Socket 通道

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

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

讀數(shù)據(jù)

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

int?bytesRead?=?socketChannel.read(buf);

寫數(shù)據(jù)

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())?{

????socketChannel.write(buf);??

} ?

ServerSocket 通道

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

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

while(true){

????SocketChannel?socketChannel?=??

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

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

}?

Datagram通道(channel 的讀寫操作與前面的有差異)

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

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

讀數(shù)據(jù)

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

buf.clear();??

channel.receive(buf);

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

寫數(shù)據(jù)

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

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

buf.clear();??

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

buf.flip(); ?

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


本文部分轉自:

Java NIO 與 IO之間的區(qū)別

Java NIO:IO與NIO的區(qū)別


個人公號:【排骨肉段】,可以關注一下。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容