1.NIO簡介
- Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java IO API。NIO與原來的IO有同樣的作用和目的,但是使用的方式完全不同,NIO支持面向緩衝區的、基於通道的IO操作。NIO將以更加高效的方式進行文件的讀寫操作。
2.NIO與IO的區別
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向緩衝區(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
無 | 選擇器 (Selectors) |
其中的阻塞和非阻塞以及Selectors是在網絡編程中涉及的。
3.緩衝區(Buffer)和通道(Channel)
- 通道(Channel)和緩衝區(Buffer)。通道表示打開到 IO 設備(例如:文件、套接字)的連接。若需要使用 NIO 系統,需要獲取用於連接 IO 設備的通道以及用於容納數據的緩衝區。然後操作緩衝區,對數據進行處理。簡而言之,Channel就類似於高速公路,Buffer就是公路上跑的真正運行貨物(信息)的貨車。
3.1 緩衝區(Buffer)
3.1.1 介紹
-
緩衝區:一個用於特定基本數據類型的容器。由 java.nio 包定義的,所有緩衝區都是 Buffer 抽象類的子類。
-
Java NIO 中的 Buffer 主要用於與 NIO 通道進行交互,數據是從通道讀入緩衝區,從緩衝區寫入通道中的。
-
Buffer 就像一個數組,可以保存多個相同類型的數據。根據數據類型不同(boolean 除外) ,有以下 Buffer 常用子類:
-
ByteBuffer
-
CharBuffer
-
ShortBuffer
-
IntBuffer
-
LongBuffer
-
FloatBuffer
-
DoubleBuffer
上述 Buffer 類 他們都採用相似的方法進行管理數據,只是各自管理的數據類型不同而已。都是通過如下方法獲取一個 Buffer對象:static XxxBuffer allocate(int capacity) : 創建一個容量爲capacity 的XxxBuffer 對象。
-
3.1.2 緩存區的基本屬性即概念
- 容量 (capacity) :表示 Buffer 最大數據容量,緩衝區容量不能爲負,並且創建後不能更改。(底層爲數組)。
- 限制 (limit) :第一個不應該讀取或寫入的數據的索引,即位於 limit 後的數據不可讀寫。緩衝區的限制不能爲負,並且不能大於其容量。
- 位置 (position) :下一個要讀取或寫入的數據的索引。緩衝區的位置不能爲負,並且不能大於其限制。
- 標記 (mark) 與重置 (reset) :標記是一個索引,通過 Buffer 中的 mark() 方法指定 Buffer 中一個特定的 position,之後可以通過調用 reset() 方法恢復到這個 position。
- 標記 、 位置 、 限制 、 容量遵守以下不變式: 0 <= mark <= position <= limit <= capacity
- 圖解基本屬性:
3.1.3 Buffer中的基本方法
- Buffer clear() :清空緩衝區並返回對緩衝區的引用;
- Buffer flip() :將緩衝區的界限設置爲當前位置,並將當前位置充值爲 0 ;
- int capacity() :返回 Buffer 的 capacity 大小;
- boolean hasRemaining():判斷緩衝區中是否還有元素;
- int limit() :返回 Buffer 的界限(limit) 的位置;
- Buffer limit(int n) :將設置緩衝區界限爲 n, 並返回一個具有新 limit 的緩衝區對象;
- Buffer mark():對緩衝區設置標記;
- int position() :返回緩衝區的當前位置 position;
- Buffer position(int n):將設置緩衝區的當前位置爲 n , 並返回修改後的 Buffer 對象;
- int remaining() :返回 position 和 limit 之間的元素個數;
- Buffer reset():將位置 position 轉到以前設置的 mark 所在的位置;
- Buffer rewind() :將位置設爲爲 0, 取消設置的 mark;
3.1.4 緩衝區的數據操作
- Buffer 所有子類提供了兩個用於數據操作的方法:get()與 put() 方法;
- 取 獲取 Buffer 中的數據
- get() :讀取單個字節
- get(byte[] dst):批量讀取多個字節到 dst 中
- get(int index):讀取指定索引位置的字節(不會移動 position)
- 放到 入數據到 Buffer 中
- put(byte b):將給定單個字節寫入緩衝區的當前位置
- put(byte[] src):將 src 中的字節寫入緩衝區的當前位置
- put(int index, byte b):將指定字節寫入緩衝區的索引位置(不會移動 position)
3.1.5 緩衝區操作Demo:
/**
*
* 此類進行nio中的buffer類的測試
* @author
* @version 1.00
* @time 2020/3/17 0017 上午 11:56
*/
public class BufferTest {
@Test
public void bufferAllocate(){
//創建一個容量爲1024的緩存區
ByteBuffer buffer = ByteBuffer.allocate(1024);
//打印 position, limit , capacity
System.out.println(buffer.position()); //0
System.out.println(buffer.limit()); //1024
System.out.println(buffer.capacity()); //1024
//放入數據
String str = "abcdef";
buffer.put(str.getBytes());
//打印position,limit,capacity
System.out.println(buffer.position()); //6
System.out.println(buffer.limit()); //1024
System.out.println(buffer.capacity()); //1024
//對putBuffer進行反轉控制
buffer.flip();
//打印position,limit,capacity
System.out.println(buffer.position()); //0
System.out.println(buffer.limit()); //6
System.out.println(buffer.capacity()); //1024
//使用get()方法讀取數據
byte[] dst = new byte[buffer.limit()];
//調用get方法進行讀取
buffer.get(dst);
//打印讀取的數據
System.out.println(new String(dst)); //abcdef
//打印position,limit,capacity
System.out.println(buffer.position()); //6
System.out.println(buffer.limit()); //6
System.out.println(buffer.capacity()); //1024
//重複讀取 rewind()
buffer.rewind();
System.out.println(buffer.position()); //0
System.out.println(buffer.limit()); //6
System.out.println(buffer.capacity()); //1024
//clear() 清空緩存區 注意的是,緩存中的數據只是“被遺忘狀態”,數據依然存在,沒有覆蓋前依然可以讀取
buffer.clear();
System.out.println(buffer.position()); //0
System.out.println(buffer.limit()); //1024
System.out.println(buffer.capacity()); //1024
//測試mark()和reset()方法
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
String string = "defg";
byteBuffer.put(string.getBytes());
System.out.println("----"+byteBuffer.limit());//1024
//反正
byteBuffer.flip();
//讀取兩個字節到字節數組
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes,0,2);
System.out.println(byteBuffer.limit()); //4
System.out.println(new String(bytes,0,2)); //de
System.out.println(byteBuffer.position()); //position 2
//標記當前position
byteBuffer.mark();
byteBuffer.get(bytes, 2, 2);
System.out.println(new String(bytes,2,2)); //fg
System.out.println(byteBuffer.position()); //position 4
//重置position
byteBuffer.reset();
System.out.println(byteBuffer.position()); //2
}
}
3.2 直接緩存區與非直接緩存區
- 直接緩衝區的創建: allocateDirect() 工廠方法 ,static XxxBuffer allocateDirect(int capacity)進行創建。
- 則 字節緩衝區要麼是直接的,要麼是非直接的。如果爲直接字節緩衝區,則 Java 虛擬機會盡最大努力直接在
機 此緩衝區上執行本機 I/O 操作。也就是說,在每次調用基礎操作系統的一個本機 I/O 操作之前(或之後),虛擬機都會盡量避免將緩衝區的內容複製到中間緩衝區中(或從中間緩衝區中複製內容)。 - 直接字節緩衝區可以通過調用此類的 allocateDirect() 工廠方法 來創建。此方法返回的 緩衝區進行分配和取消分配所需成本通常高於非直接緩衝區 。直接緩衝區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程序的內存需求量造成的影響可能並不明顯。所以,建議將直接緩衝區主要分配給那些易受基礎系統的
機 本機 I/O 操作影響的大型、持久的緩衝區。一般情況下,最好僅在直接緩衝區能在程序性能方面帶來明顯好處時分配它們。 - 直接字節緩衝區還可以過 通過FileChannel 的 map() 方法 將文件區域直接映射到內存中來創建 。該方法返回MappedByteBuffer 。Java 平臺的實現有助於通過 JNI 從本機代碼創建直接字節緩衝區。如果以上這些緩衝區中的某個緩衝區實例指的是不可訪問的內存區域,則試圖訪問該區域不會更改該緩衝區的內容,並且將會在訪問期間或稍後的某個時間導致拋出不確定的異常。
- 字節緩衝區是直接緩衝區還是非直接緩衝區可通過調用其 isDirect() 方法來確定。提供此方法是爲了能夠在
性能關鍵型代碼中執行顯式緩衝區管理 。 - 總結一下:非直接緩存區硬盤和用戶之間傳輸數據經歷了系統內核地址空間(OS)和用戶地址空間(JVM)之間的數據複製,效率較低。直接緩存區直接採用系統內核地址空間和用戶地址空間的物理內存映射文件,省去了數據複製,效率高,但是因爲是物理內存映射文件不再JVM中,所以創建後不受Java程序控制。