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