(本文主體為譯文)自jdk1.4開始,java新添加了一種IO接口--NIO,相較傳統的IO,它提供了一種新的方式。
在傳統IO中,我們使用字節流與字符流,而在NIO中用的是channels 與 buffers,數據會從channel中讀入到buffer中,或者從buffer中寫入到channel中。
JavaNIO讓我們可以使用非阻塞IO,例如,線程向channel提出讀取數據到buffer的請求,當channel向buffer中讀入數據的時候,線程可以做一些其他的操作。當數據被讀入buffer後,線程可以繼續處理,當數據從buffer寫入到channel的時候,也是一樣。
JavaNIO中有“selector”概念,selector是一個用於監聽大量channel中時間的對象。比如,打開鏈接,數據到達等,所以一個單獨的線程可以監聽大量的channel中的數據事件。
JavaNIO中有這麼幾個主要的組件:Channels、Buffers、Selectors。JavaNIO的類、組件有很多,但這三個是最核心的API.其他類似Pipe與FileLock等其他的組件,也都是跟這三個核心組件組合使用的。我們在剛開始學習NIO時,需要將注意力集中到這三個核心組件。
NIO的所有IO操作都是從Channel開始,Channel有一點想Stream。數據可以從Channel讀入到Buffer中,數據可以從Buffer中寫入到Channel中。
NIO中有多種Channel與Buffer類型。Channel類型主要有FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。Channels覆蓋了UDP、TCP網絡IO以及文件IO。
Buffer類型主要有ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。Buffers覆蓋了可以通過IO發送的基礎數據類型。另外,還有一個MappedByteBuffer,用於內存與文件的映射操作。
Selector使一個線程可以處理多個Channel。如果應用系統打開了很多鏈接,但每個鏈接的業務量比較低,Selector的處理方式會很方便。使用Selector需要使用select()先對目標監聽的Channel進行註冊。這個方法在已經註冊的channels有事件準備好之前會一直阻塞。一旦select()有了返回值,線程就可以繼續處理這些事件。比如接入事件、數據到達事件等。
Channel簡述
JavaNIO Channels與streams非常相似,又略有不同。
Channels是一個雙工通道,可以進行讀寫。Streams是一個單工通道,進行讀或寫都需要專門的api,比如inputStream,outputStream。
Channels可以進行異步的讀寫操作。
數據是從Channel中讀入Buffer,或者從Buffer寫入Channels中。
Channel類型主要有FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel。字如其能。
Buffer讀寫基本步驟
在JavaNIO中,通過Buffer與Channel的交互來實現數據的讀寫。Buffer本質上是一個內存數據塊,Buffer封裝了方便對這個數據塊操作的方法集。
使用Buffer讀寫數據基本上有4步:
1、將數據寫入Buffer
2、調用Buffer的flip()
3、從Buffer中讀取數據
4、調用Buffer的clear()或者compact()方法
在像buffer中寫入數據的時候,buffer將記錄已經寫入了多少數據。當需要讀取數據時,需要通過Buffer的flip()將buffer的模式切換到讀模式。
當buffer中的數據讀取完畢後,需要對buffer進行清理,使buffer可以進行再次的寫入。清理buffer有兩種方式,一種是調用clear().另一種是調用compact(),它只清空已讀數據,未讀數據將會移植buffer的開始,新寫入的數據將會接著未讀數據繼續寫入。
Buffer的關鍵屬性
我們需要明確Buffer的capacity、position、limit這三個屬性的作用。
capacity的意義是容量,無論讀寫模式,buffer的容量不會有變化,是一個固定值。無論寫入何種類型數據,數據的長度都不會變。buffer寫滿後,只有執行清理操作後才能二次使用。
position與limit兩個屬性是變動值。
我們只能像buffer中的特定位置寫入數據。position的初始值是0,當數據寫入Buffer時,position的值會指向下一個可以寫入數據的位置,position的最大值是capacity-1。
當你從Buffer中讀取數據的時候,隨著flip()的調用,position的值被重置為0,在讀取數據的時候,position的值總是指向下一個可以讀取的位置。
寫模式時,limit的值表示你可向buffer中寫入多少數據,這時,limit的值與capacity是相同的值,比如我們指定capacity=48,那麼limit的值就是48。當模式切換為讀模式時,limit表示你可以從buffer中讀取多少數據,也就是當切換為讀模式時,limit的值會設為前一刻寫模式時position的值。
特別說明:flip()的作用是將buffer的模式從寫模式切換為讀模式。調用flip(),limit的值會被設置為此時position的值,然後position的值會被置0。
<code>public final Buffer flip() {/<code>
<code> limit = position;/<code>
<code> position = 0;/<code>
<code> mark = -1;/<code>
<code> return this;/<code>
<code>}/<code>
Buffer的關鍵方法
我們通過Buffer的allocate()分配一段內存塊來創建Buffer對象。各類型的Buffer類都具備這個allocate方法。allocate()實現為靜態方法。比如,
ByteBuffer buf=ByteBuffer.allocate(64);//分配一個容量為64的字節buffer
Charbuffer buf1=CharBuffer.allocate(1024);//分配一個容量為1024的字符buffer
我們在buffer中寫入數據時有兩種情況:
1、從Channel中讀取數據到buffer中。這時我們使用Channel的read(Buffer buf)
2、主動向buffer中寫入數值,調用Buffer的put();put方法有多種重寫方法。
從buffer中讀取數據時也是兩種情況:
1、從buffer中讀取數據寫入Channel中,調用Channel的write(Buffer buf)方法。
2、從buffer中讀取數據進行業務操作,調用Buffer的get(),同樣的get方法也有多種重寫。
在這裡,注意讀寫操作時的主題對象是buffer.
rewind()的方法可以即將positon的值置0,當有多次讀取buffer數據時,使用這個方法。
在讀操作完成後,我們需要調用clear()或compact(),使buffer可以繼續寫入,調用clear()時,postion的值被置0,limit的值將置為capacity的值。如果在業務處理中,buffer的值並未被完全讀取,這時調用clear(),我們沒有標識去標記我們讀了多少,還有多少沒讀取。Buffer的具體實現中,具體的數據都是以數組來承載的,clear方法並沒有對數組進行清空,而是靠屬性值在標識,buffer使用中是對數組中的值進行重寫操作。
<code>public final Buffer clear() {/<code>
<code> position = 0;/<code>
<code> limit = capacity;/<code>
<code> mark = -1;/<code>
<code> return this;/<code>
<code>}/<code>
compact()將未讀取的數據拷貝紙Buffer內部數組的開始位置,再寫入的數據會挨著未讀取的數據中最後一個元素繼續寫入。在數據寫入中,limit的值保持為capacity的值不變。
我們可以通過Buffer的mark()獲取當前position的值從而標記一個位置,之後,我們可以通過調用reset()將position的值重置為mark()所標記的值。
equals()用於比較兩個buffer是否相等,基本的比較邏輯是:
1、兩個buffer具有相同的類型
2、buffer中當前存在的數據元素數量相等
3、buffer中當前各個數據元素相等
compareTo()用於比較兩個buffer,比如可以用作排序,基本的比較邏輯是:
1、取兩個buffer中剩餘元素的數量較小的值作為比較次數,依次比較兩個buffer中當前存在的元素,首先出現大的元素的buffer為大的一方;
2、如果參與比較各個元素等都相等,則剩餘元素數量多的buffer為大的一方。
javaNIO的 分散/匯聚
JavaNIO內部提供了 分散讀/匯聚寫支持,這是用於從channel讀取及寫入數據的概念。
分散讀是說將channel的數據讀入到多個buffer中。
匯聚寫將多個buffer中的數據寫到同一個channel中。
分散讀/匯聚寫在處理需要對數據的不同部分進行不同處理的操作時比較實用。
先看一下分散讀:
<code>ByteBuffer header = ByteBuffer.allocate(128);/<code>
<code>ByteBuffer body = ByteBuffer.allocate(1024);/<code>
<code>ByteBuffer[] bufferArray = { header, body };/<code>
<code>channel.read(bufferArray);/<code>
需要注意的是,使用分散讀操作時,我們構造的消息結構應該符合我們所構造的數組中各個buffer所指定的大小。比如上面代碼中有兩部分,那麼我們可以確認的是header 部分的長度就是128,是固定值。
看一下匯聚寫:
<code>ByteBuffer header = ByteBuffer.allocate(128);/<code>
<code>ByteBuffer body = ByteBuffer.allocate(1024);/<code>
<code>//write data into buffers/<code>
<code>ByteBuffer[] bufferArray = { header, body };/<code>
<code>channel.write(bufferArray);/<code>
這裡,我們在寫入數據的時候,不像分散讀的約束那麼強,也就是說,header指定了128,但是我們只寫入了不足128的數據,並不影響數據的正常向channel寫入。
這裡多少有點,想用我的就得聽我的,給出去的就不關我事了。O__O "…
JavaNIO Channel to Channel的數據傳輸
在JavaNIO中,在其中一個channel是FileChannel時,我們可以將數據從一個channel直接傳給另一個channel,FileChannel有transferTo與transferFrom兩個方法來實現這個功能。
FileChannel的transferFrom()將數據從源channel傳入目標FileChannel。
<code>RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");/<code>
<code>FileChannel fromChannel = fromFile.getChannel();/<code>
<code>RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");/<code>
<code>FileChannel toChannel = toFile.getChannel();/<code>
<code>long position = 0;/<code>
<code>long count = fromChannel.size();/<code>
<code>toChannel.transferFrom(fromChannel, position, count);/<code>
我們的目標:讓生活更美好。
閱讀更多 BlackMan 的文章