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));
本文部分轉自:
個人公號:【排骨肉段】,可以關注一下。