Java NIO之緩沖區(qū)
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??油多壞不了菜
來源 |? urlify.cn/JrAnIb
最近打算把Java網(wǎng)絡編程相關的知識深入一下(IO、NIO、Socket編程、Netty)
Java NIO主要需要理解緩沖區(qū)、通道、選擇器三個核心概念,作為對Java I/O的補充, 以提升大批量數(shù)據(jù)傳輸?shù)男省?/p>
學習NIO之前最好能有基礎的網(wǎng)絡編程知識
Java I/O流
Java 網(wǎng)絡編程
基礎
除了需要對 Java 網(wǎng)絡編程有一定了解,還需要對用戶空間、內(nèi)核空間、內(nèi)存空間多重映射等知識有一定了解
用戶空間與內(nèi)核空間
為了提供操作系統(tǒng)的穩(wěn)定性,操作系統(tǒng)將虛擬地址空間分為用戶空間和內(nèi)核空間
其中用戶進程(我們自己的程序)只能操作用戶空間
I/O過程中的數(shù)據(jù)流向
假設我們需要從磁盤中某個文件讀取數(shù)據(jù)。進程發(fā)起read()系統(tǒng)調(diào)用,進入內(nèi)核態(tài),內(nèi)核隨即向磁盤控制硬件發(fā)出命令, 要求其從磁盤讀取數(shù)據(jù),磁盤控制器將數(shù)據(jù)直接寫入到內(nèi)核內(nèi)存緩沖區(qū)中(這一步DMA完成,不需要CPU參與),隨后內(nèi)核把數(shù)據(jù)從內(nèi)核空間的臨時緩沖區(qū)拷貝到用戶緩沖區(qū)(需要CPU參與),進程切換回用戶態(tài)繼續(xù)執(zhí)行。
總結(jié)起來的數(shù)據(jù)流向是:磁盤 ---> 內(nèi)核緩沖區(qū) ---> 用戶緩沖區(qū)
那么問題來了:內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到用戶緩沖區(qū)的這一步顯得有點多余,是否可以避免?
內(nèi)存空間多重映射
我們知道對于虛擬地址空間,一個以上的虛擬地址可指向同一個物理內(nèi)存地址
如果把用戶空間的虛擬地址和內(nèi)核空間的虛擬地址映射到同一個物理地址,那么這塊物理地址代表的空間就對內(nèi)核和用戶進程都可見了??!便可省去數(shù)據(jù)在內(nèi)核緩沖區(qū)和用戶緩沖區(qū)來回復制的開銷。(這便是直接緩沖區(qū)的思想)
緩沖區(qū)(Buffer)
Java NIO數(shù)據(jù)傳輸過程:數(shù)據(jù)先放到發(fā)送緩沖區(qū) --> 通過通道發(fā)送到接收端 ---> 接受端通道接受數(shù)據(jù)并填充到接受緩沖區(qū)
所以緩沖區(qū)的作用其實是連接通道作為數(shù)據(jù)傳輸?shù)哪繕嘶蛘邅碓矗ɑ蛘哒f緩沖區(qū)是通道的輸入或者輸出)
核心概念
屬性
要理解Buffer的工作機制,首先要了解幾個屬性的意義
capacity(容量) 緩沖區(qū)的容量,創(chuàng)建緩沖區(qū)時指定
position(位置)下一個要被讀取或者寫入的元素的索引
limit(上界)?緩沖區(qū)中第一個不能被讀或者寫的位置
mark(標記)一個備忘位置
其中 mark <= position <= limit <= capacity,對limit和position兩個屬性的理解非常重要
存取
緩沖區(qū)的核心就在于存取操作,Buffer提供了相對位置存取和絕對位置存取兩種方式
相對位置存取:在當前position位置寫入或者讀取數(shù)據(jù), 然后增加position的值
絕對位置存?。涸谥付ǖ奈恢脤懭牖蛘咦x取數(shù)據(jù),不改變position的值
?//相對位置存取
?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)(flip)
翻轉(zhuǎn)是Buffer的核心概念,我們可以理解Buffer有兩種模式:寫模式和讀模式
寫模式下,我們分配一個緩沖區(qū),然后直接填充數(shù)據(jù)(position的值從0開始遞增);讀模式下, 我們也從頭開始讀取數(shù)據(jù)(position從0開始遞增)
那么我們怎么從寫模式切換到讀模式吶?翻轉(zhuǎn)!!翻轉(zhuǎn)的時候我們用limit記錄待讀取數(shù)據(jù)的長度, 然后將position置為0 就可以開始讀取數(shù)據(jù)了
下為翻轉(zhuǎn)的源碼
public?final?Buffer?flip()?{
???//記錄待讀取數(shù)據(jù)的長度
????limit?=?position;
???//從頭開始讀取數(shù)據(jù)
????position?=?0;
????mark?=?-1;
????return?this;
}
demo
一個完整的例子
//創(chuàng)建一個緩沖區(qū)?
ByteBuffer?buffer?=?ByteBuffer.allocate(100);
//寫數(shù)據(jù)
for?(char?c?:?"hello".toCharArray())?{
??buffer.put((byte)?c);
}
//翻轉(zhuǎn)
buffer.flip();//等價于?buffer.limit(buffer.position()).position(0);
//讀數(shù)據(jù)
while?(buffer.hasRemaining())?{
??char?c?=?(char)?buffer.get();
??System.out.println(c);
}
創(chuàng)建緩沖區(qū)
Buffer不能直接通過構(gòu)造函數(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)
直接緩沖區(qū)(DirectByteBuffer)
對于一般的I/O過程,數(shù)據(jù)的流向總是:磁盤或者網(wǎng)絡 --> 內(nèi)核臨時緩沖區(qū) --> 用戶空間緩沖區(qū),其中內(nèi)核空間臨時緩沖區(qū)到用戶空間緩沖區(qū)復制這一步顯得有點多余??!
直接緩沖區(qū)解決了這個問題, 直接緩沖區(qū)對內(nèi)核和用戶空間都可見,這樣就可以避免"內(nèi)核臨時緩沖區(qū)到用戶空間緩沖區(qū)"復制的開銷
雖然直接緩沖區(qū)是I/O的最佳選擇,但是其比創(chuàng)建非直接緩沖區(qū)花費更大的成本,所以我們對直接緩沖區(qū)一般都會重復使用(每次使用都創(chuàng)建的話成本就太高了)。
總結(jié)
本文主要講解NIO學習需要掌握的一些基礎知識以及緩沖區(qū)的使用,重點是對直接緩沖區(qū)的理解。
粉絲福利:Java從入門到入土學習路線圖
???

?長按上方微信二維碼?2 秒
感謝點贊支持下哈?
