Java NIO提供了與標準IO不同的IO工作方式:?
Channels and Buffers(通道和緩沖區(qū)):標準的IO基于字節(jié)流和字符流進行操作的,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進行操作哗戈,數據總是從通道讀取到緩沖區(qū)中唯咬,或者從緩沖區(qū)寫入到通道中畏浆。
Asynchronous IO(異步IO):Java NIO可以讓你異步的使用IO,例如:當線程從通道讀取數據到緩沖區(qū)時蜀涨,線程還是可以進行其他事情。當數據被寫入到緩沖區(qū)時,線程可以繼續(xù)處理它草娜。從緩沖區(qū)寫入通道也類似痒筒。
Selectors(選擇器):Java NIO引入了選擇器的概念簿透,選擇器用于監(jiān)聽多個通道的事件(比如:連接打開老充,數據到達)啡浊。因此巷嚣,單個的線程可以監(jiān)聽多個數據通道窘拯。
阻塞IO和非阻塞IO
Java IO流都是阻塞的涤姊,這意味著嗤放,當一條線程執(zhí)行read()或者write()方法時,這條線程會一直阻塞直到讀取到了一些數據或者要寫出去的數據已經全部寫出斤吐,在這期間這條線程不能做任何其他的事情搔涝。
java NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式和措,阻塞模式的NIO除了使用Buffer存儲數據外和IO基本沒有區(qū)別)允許一條線程從channel中讀取數據,通過返回值來判斷buffer中是否有數據派阱,如果沒有數據,NIO不會阻塞,因為不阻塞這條線程就可以去做其他的事情文兑,過一段時間再回來判斷一下有沒有數據盒刚。NIO的寫也是一樣的,一條線程將buffer中的數據寫入channel因块,它不會等待數據全部寫完才會返回籍铁,而是調用完write()方法就會繼續(xù)向下執(zhí)行
選擇器(Selectors)
?Java NIO的選擇器允許一個單獨的線程來監(jiān)視多個輸入通道拒名,你可以注冊多個通道使用一個選擇器雁佳,然后使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道甘穿。這種選擇機制温兼,使得一個單獨的線程很容易來管理多個通道。
面向流與面向緩沖
Java IO和NIO之間第一個最大的區(qū)別是武契,IO是面向流的募判,NIO是面向緩沖區(qū)的。 Java IO面向流意味著每次從流中讀一個或多個字節(jié)咒唆,直至讀取所有字節(jié)届垫,它們沒有被緩存在任何地方。此外全释,它不能前后移動流中的數據装处。如果需要前后移動從流中讀取的數據,需要先將它緩存到一個緩沖區(qū)浸船。 Java NIO的緩沖導向方法略有不同妄迁。數據讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動李命。這就增加了處理過程中的靈活性登淘。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數據封字。而且黔州,需確保當更多的數據讀入緩沖區(qū)時耍鬓,不要覆蓋緩沖區(qū)里尚未處理的數據。
Java NIO 由以下幾個核心部分組成:
Channels/Buffers/Selectors
?數據可以從Channel讀到Buffer中流妻,也可以從Buffer 寫到Channel中牲蜀。
Channel
Channel的實現(xiàn):(涵蓋了UDP和TCP網絡IO以及文件IO)
FileChannel、DatagramChannel绅这、SocketChannel涣达、ServerSocketChannel
讀數據:int bytesRead = inChannel.read(buf);
寫數據: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使用:
讀數據:
flip()方法:將Buffer從寫模式切換到讀模式伏钠;調用flip()方法會將position設回0,并將limit設置成之前position的值谨设。
(char) buf.get():讀取數據
rewind():將position設回0熟掂,所以你可以重讀Buffer中的所有數據;limit保持不變扎拣,仍然表示能從Buffer中讀取多少個元素(byte赴肚、char等)。
mark()方法:可以標記Buffer中的一個特定position二蓝。
reset()方法:恢復到Buffer.mark()標記時的position誉券。
一旦讀完了所有的數據,就需要清空緩沖區(qū)刊愚,讓它可以再次被寫入踊跟。
clear()方法:清空整個緩沖區(qū);position將被設回0鸥诽,limit被設置成 capacity的值商玫。
compact()方法:只會清除已經讀過的數據;任何未讀的數據都被移到緩沖區(qū)的起始處牡借,新寫入的數據將放到緩沖區(qū)未讀數據的后面决帖;將position設到最后一個未讀元素正后面,limit被設置成 capacity的值蓖捶。
寫數據
buf.put(127); ?
Buffer的三個屬性:
capacity:含義與模式無關地回;Buffer的一個固定的大小值;Buffer滿了需要將其清空才能再寫;
ByteBuffer.allocate(48)刻像;該buffer的capacity為48byte
CharBuffer.allocate(1024);該buffer的capacity為1024個char?
position:含義取決于Buffer處在讀模式還是寫模式(初始值為0畅买,寫或者讀操作的當前位置)
寫數據時,初始的position值為0细睡;其值最大可為capacity-1
將Buffer從寫模式切換到讀模式谷羞,position會被重置為0
limit:含義取決于Buffer處在讀模式還是寫模式(寫limit=capacity;讀limit等于最多可以讀取到的數據)
寫模式下溜徙,limit等于Buffer的capacity
切換Buffer到讀模式時湃缎, limit表示你最多能讀到多少數據;
Selector
Selector允許單線程處理多個 Channel蠢壹。如果你的應用打開了多個連接(通道)嗓违,但每個連接的流量都很低,使用Selector就會很方便图贸。例如蹂季,在一個聊天服務器中。
要使用Selector疏日,得向Selector注冊Channel偿洁,然后調用它的select()方法。這個方法會一直阻塞到某個注冊的通道有事件就緒沟优。一旦這個方法返回涕滋,線程就可以處理這些事件,事件的例子有如新連接進來挠阁,數據接收等宾肺。?
創(chuàng)建:
Selector selector = Selector.open(); ?
注冊通道:
channel.configureBlocking(false); ?
/*與Selector一起使用時,Channel必須處于非阻塞模式鹃唯,這意味著不能將FileChannel與Selector一起使用爱榕,因為FileChannel不能切換到非阻塞模式(而套接字通道都可以)*/
SelectionKey key = channel.register(selector,?Selectionkey.OP_READ);?
/*第二個參數表明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(); ?
讀數據
ByteBuffer?buf?=?ByteBuffer.allocate(48);
int?bytesRead?=?inChannel.read(buf);
寫數據
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));
讀數據
ByteBuffer?buf?=?ByteBuffer.allocate(48);
int?bytesRead?=?socketChannel.read(buf);
寫數據
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));
讀數據
ByteBuffer?buf?=?ByteBuffer.allocate(48);
buf.clear();??
channel.receive(buf);
/*receive()方法會將接收到的數據包內容復制到指定的Buffer. 如果Buffer容不下收到的數據,多出的數據將被丟棄弟晚。 */
寫數據
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));
本文部分轉自:
個人公號:【排骨肉段】忘衍,可以關注一下逾苫。