NIO是同步非阻塞的。NIO裏面有幾個概念需要了解:緩衝區(Buffer)、選擇器(Selector)、通道(Channel)。
1.緩衝區(Buffer):
緩衝區實際上是一個容器對象,更直接的說,其實就是一個數組,在 NIO 庫中,所有數據都是用緩衝區處理的。在讀
取數據時,它是直接讀到緩衝區中的; 在寫入數據時,它也是寫入到緩衝區中的;任何時候訪問 NIO 中的數據,都
是將它放到緩衝區中。
而在面向流I/O 系統中,所有數據都是直接寫入或者直接將數據讀取到 Stream 對象中。
在NIO 中,所有的緩衝區類型都繼承於抽象類 Buffer,最常用的就是 ByteBuffer,對於 Java 中的基本類型,基本都有
一個具體 Buffer 類型與之相對應,它們之間的繼承關係如下圖所示:
2.Buffer的基本使用方法:
我們說過Buffer是一個特殊數組。它裏面有幾個屬性:
position:指定下一個將要被寫入或者讀取的元素索引,它的值由 get()/put()方法自動更新,在新創建一個 Buffer 對象
時,position 被初始化爲0。
limit:指定還有多少數據需要取出(在從緩衝區寫入通道時),或者還有多少空間可以放入數據(在從通道讀入緩衝區時)。
capacity:指定了可以存儲在緩衝區中的最大數據容量,實際上,它指定了底層數組的大小,或者至少是指定了准許我
們使用的底層數組的容量。
上面屬性的關係:0 <=position <= limit <=capacity。
我們寫個代碼來看下這三個屬性:
準備一個test文檔,存放的 D盤,輸入以下內容:
下面寫段代碼讀取D盤這個文件:
package com.gupaoedu.vip.netty.io.nio.buffer; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.FileChannel; public class BufferDemoTest { public static void main(String[] args) throws IOException { //用文件io讀取d盤裏面的文件 FileInputStream fileInput=new FileInputStream ("D://test.txt"); //操作文件的管道 FileChannel channel=fileInput.getChannel (); //新建一個10大小的緩衝區。(10個大小的byte數組) ByteBuffer buffer= ByteBuffer.allocate (10); outPrint("初始化",buffer); channel.read (buffer); outPrint("調用read方法",buffer); //準備操作之前,先鎖定操作範圍 buffer.flip(); outPrint("調用flip()", buffer); //判斷有沒有可讀數據 while (buffer.remaining() > 0) { byte b = buffer.get(); System.out.print(((char)b)); } outPrint("調用get()", buffer); //可以理解爲解鎖 buffer.clear(); outPrint("調用clear()", buffer); //最後把管道關閉 channel.close(); } //獲取每個階段的buffer的三個屬性 public static void outPrint(String step,ByteBuffer buffer){ System.out.println (step+":"); //容量,數組大小 System.out.print("capacity: " + buffer.capacity() + ", "); //buffer裏面的position屬性(當前操作數據所在的位置,也可以叫做遊標) System.out.print("position: " + buffer.position() + ", "); //鎖定值,flip,數據操作範圍索引只能在position - limit 之間 System.out.println("limit: " + buffer.limit()); } }
輸出結果:
初始化:
capacity: 10, position: 0, limit: 10
調用read方法:
capacity: 10, position: 4, limit: 10
調用flip():
capacity: 10, position: 0, limit: 4
test調用get():
capacity: 10, position: 4, limit: 4
調用clear():
capacity: 10, position: 0, limit: 10
看着代碼分析下:
初始化:capacity: 10, position: 0, limit: 10 畫圖如下:
在調用read方法,把數據讀取到緩存區裏面, 如果讀取4個自己的數據,則此時position 的值爲 4,即下一個將要被寫入的字節索引爲 4,而 limit 仍然是10,如下圖所示:
下一步把讀取的數據寫入到輸出通道中,相當於從緩衝區中讀取數據,在此之前,必須調用 flip()方法,該方法將會完
成兩件事情:
1. 把limit 設置爲當前的 position 值
2. 把position 設置爲0
因爲position 爲0,limit爲我們讀取讀取數據的最後一個位置。我們就可以用position 來讀取放在緩存區裏面的數據。
現在調用 get()方法從緩衝區中讀取數據寫入到輸出通道,這會導致position 的增加而 limit 保持不變,但 position 不
會超過limit 的值,所以在讀取我們之前寫入到緩衝區中的 4個自己之後,position 和 limit的值都爲 4,如下圖所示:
在從緩衝區中讀取數據完畢後,limit的值仍然保持在我們調用 flip()方法時的值,調用 clear()方法能夠把所有的狀態變
化設置爲初始化時的值,如下圖所示:
3.緩衝區的分配
我們看上面代碼,在創建一個緩衝區對象時,會調用靜態方法allocate()來指定緩衝區的容量,其實調用
allocate()相當於創建了一個指定大小的數組,並把它包裝爲緩衝區對象。或者我們也可以直接將一個現有的數組,包裝爲緩衝區對象,如下示例代碼所示:
public class BufferWrapTest { public static void main(String[] args) { // 分配指定大小的緩衝區 ByteBuffer buffer1 = ByteBuffer.allocate(10); // 包裝一個現有的數組 byte array[] = new byte[10]; ByteBuffer buffer2 = ByteBuffer.wrap( array ); } }