本文轉載自微信公眾號「小明菜市場」,作者小明菜市場。轉載本文請聯系小明菜市場公眾號。
前言
Java NIO 需要理解的主要有緩沖區,通道,選擇器,這三個主要的部分。
基礎
用戶空間和內核空間
操作系統為了提供穩定性,把虛擬地址空間分為用戶空間和內核空間,其中用戶進程只能操作用戶空間的內容,而內核空間的內容可以操作用戶空間的內容以及用戶空間的內容。
I/O過程中的數據流向
假設我們需要從磁盤中的某個文件讀取數據,進程發起read系統調用,進入內核狀態,內核會隨即向磁盤控制硬件發出命令,要求其從磁盤讀取數據,磁盤控制器把數據直接寫入到內核緩沖區中,隨后內核會吧數據從內核空間的臨時緩沖區拷貝到用戶緩沖區,進程再次切換回用戶態繼續執行。總結數據流向是:磁盤 -> 內核緩沖區 -> 用戶緩沖區
內存空間多重映射
對于虛擬地址的空間,一個以上的虛擬地址可以指向同一個物理內存地址。如果用戶空間的虛擬地址和內核空間的虛擬地址映射到同一個物理地址,那么這塊物理地址代表的空間就對內核和用戶進程都可見。便可省去數據在內核緩沖區和用戶緩沖區來回復制的開銷。
緩沖區
Java NIO 數據傳輸過程,數據先放到發送緩沖區 -> 通過通道發送到接收端 -> 接受端通道接受數據并填充到接受緩沖區 所以緩沖區的作用其實是連接通道作為數據傳輸的目標或者來源。
核心概念
屬性
需要理解Buffer工作機制,需要了解如下幾個屬性
- 容量: 緩沖區的容量,創建緩沖區時指定
- 位置: 下一個要被讀取或者寫入元素的索引
- 上界: 緩沖區中第一個不能被讀或者寫的位置。
- mark標記,一個備忘的位置
存取
緩沖區的核心就在于存取操作,buffer提供了相對位置存取和絕對位置存取兩種方式。
- 相對位置存取:在當前的位置寫入或者讀取數據,然后增加位置的值。
- 絕對位置存取。在指定的位置的寫入或者讀取數據,不改變位置的值
代碼如下
//相對位置存取
public abstract ByteBuffer put(byte b);
public abstract byte get();
//絕對位置存取
public abstract ByteBuffer put(int index, byte b);
public abstract byte get(int index);
翻轉
翻轉是 buffer的核心概念,可以理解buffer有兩種模式,寫模式和讀模式。寫模式:我們分配一個緩沖區,然后直接填充數據,讀模式下。我們從頭開始讀取數據。如何從寫模式切換到讀模式,翻轉,翻轉的時候我們用limit記錄待讀取數據的長度,然后把位置置換為0就可以開始讀取數據了。
public final Buffer flip() {
//記錄待讀取數據的長度
limit = position;
//從頭開始讀取數據
position = 0;
mark = -1;
return this;
}
demo
//創建一個緩沖區
ByteBuffer buffer = ByteBuffer.allocate(100);
//寫數據
for (char c : "hello".toCharArray()) {
buffer.put((byte) c);
}
//翻轉
buffer.flip();//等價于 buffer.limit(buffer.position()).position(0);
//讀數據
while (buffer.hasRemaining()) {
char c = (char) buffer.get();
System.out.println(c);
}
直接緩沖區
對于一般的I/O過程,數據流向是,磁盤或者網絡 -> 內核臨時緩沖區 -> 用戶空間緩沖區
直接緩沖區解決的是內核空間臨時緩沖區到用戶空間緩沖區復制這一步耗費的多余。雖然直接緩沖區是I/O的最佳選擇,但是其比創建非直接緩沖區將會耗費更大的成本了,所以一般都是直接重復使用。
創建緩沖區
Buffer不能直接通過構造函數實例化,都是通過靜態工廠方法創建,下為ByteBuffer的靜態工廠方法。
//創建內存緩沖區
public static ByteBuffer allocate(int capacity);
//創建直接緩沖區
public static ByteBuffer allocateDirect(int capacity) ;
public static ByteBuffer wrap(byte[] array, int offset, int length)
原文地址:https://mp.weixin.qq.com/s/EP5H2uNT7NlA732UMclACg