[NIO系列]NIO源码分析之Buffer
在以前的一篇文章中我們介紹過(guò)IO模型
IO模型總結(jié) http://www.cnblogs.com/coldridgeValley/p/5449758.html
,而在實(shí)際運(yùn)用中多路復(fù)用IO使用很多,JDK早在1.4的時(shí)候就引入了NIO(new IO),今天我們來(lái)學(xué)習(xí)NIO基礎(chǔ)組件之一的Buffer的相關(guān)原理。
Java NIO由以下幾個(gè)核心部分組成:
- Buffer
- Channel
- Selector
傳統(tǒng)的IO操作是面向數(shù)據(jù)流的,這樣就意味著數(shù)據(jù)從流中讀取出來(lái)直到讀取完畢都是沒(méi)有任何地方可以緩存的。而NIO操作面向的是緩沖區(qū),數(shù)據(jù)從Channel中讀取到Buffer緩沖區(qū),在Buffer中處理數(shù)據(jù),本文我們從源碼角度來(lái)解析緩沖區(qū)Buffer的代碼實(shí)現(xiàn),從而理解其原理。
Buffer
A container for data of a specific primitive type.
A buffer is a linear, finite sequence of elements of a specific primitive type. Aside from its content, the essential properties of a buffer are its capacity, limit, and position:
A buffer's capacity is the number of elements it contains. The capacity of a buffer is never negative and never changes.
A buffer's limit is the index of the first element that should not be read or written. A buffer's limit is never negative and is never greater than its capacity.
A buffer's position is the index of the next element to be read or written. A buffer's position is never negative and is never greater than its limit.
There is one subclass of this class for each non-boolean primitive type.
上文是JDK文檔中對(duì)于Buffer的描述,簡(jiǎn)單而言就是Buffer是一個(gè)線(xiàn)性的,有限容量的基本數(shù)據(jù)類(lèi)型容器,對(duì)于所有的非布爾型變量,都有一種buffer可以與之對(duì)應(yīng)。Buffer這個(gè)容器中有幾個(gè)重要的變量:
- capacity:表示buffer的容量,固定變量。
- limit:表示讀寫(xiě)的邊界位置,讀寫(xiě)的數(shù)據(jù)不會(huì)超過(guò)此值,改值永遠(yuǎn)不大于capacity。
- position:表示下次讀寫(xiě)開(kāi)始的位置,大小永遠(yuǎn)不大于limit。
引用一幅網(wǎng)上流傳非常廣泛的圖
看起來(lái)還是比較容易理解的。
Java NIO有如下常用的buffer類(lèi)型:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- ShortBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
可以看到Buffer的類(lèi)型代表不同的數(shù)據(jù)類(lèi)型,也就是說(shuō)Buffer中存放的數(shù)據(jù)可以是基本數(shù)據(jù)類(lèi)型,MappedByteBuffer稍有不同,會(huì)單獨(dú)介紹。
我們?cè)诳碆uffer的源碼之前先來(lái)看個(gè)Buffer的Demo
public class BufferDemoOne {public static void main(String[] args)throws Exception {testBuffer();}private static void testBuffer() throws Exception{RandomAccessFile file = new RandomAccessFile("/test.txt", "rw");FileChannel channel = file.getChannel();ByteBuffer buffer = ByteBuffer.allocate(1024);int read = channel.read(buffer);while (read != -1) {buffer.flip();while (buffer.hasRemaining()) {System.out.print((char)buffer.get());}buffer.clear();read = channel.read(buffer);}file.close();} }可以看到利用Buffer來(lái)讀寫(xiě)數(shù)據(jù)基本包含四步:
- 數(shù)據(jù)寫(xiě)入buffer
- 調(diào)用flip()將buffer轉(zhuǎn)換為讀模式
- 從buffer中讀取數(shù)據(jù)
- 調(diào)用buffer.clear()清空數(shù)據(jù)
ByteBuffer是我們最常用的Buffer之一,接下來(lái)我們從源碼角度一步一步來(lái)分析其內(nèi)部一些重要方法的實(shí)現(xiàn)。
put
我們正常出了使用channel向buffer中寫(xiě)入數(shù)據(jù),還有其本身的put方法,put方法的重載版本很多。我們這里找最簡(jiǎn)單的放入一個(gè)byte數(shù)據(jù)看下:
public ByteBuffer put(byte x) {hb[ix(nextPutIndex())] = x;return this;}final int nextPutIndex() { // package-privateif (position >= limit)throw new BufferOverflowException();return position++;}很簡(jiǎn)單,把內(nèi)部的position指針累加,同時(shí)把byte數(shù)據(jù)存放在內(nèi)部的byte數(shù)組中。
mark
public final Buffer mark() {mark = position;return this;}mark變量是一個(gè)ByteBuffer特有的變量,mark()方法的目的就是將position值賦給mark變量從而達(dá)到記錄position的目的。
reset
public final Buffer reset() {int m = mark;if (m < 0)throw new InvalidMarkException();position = m;return this;}利用mark中記錄下來(lái)的position進(jìn)行恢復(fù),所以叫reset
flip
public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}flip的作用是將buffer的模式從寫(xiě)模式轉(zhuǎn)換為讀模式,所以我們可以看到,通過(guò)flip調(diào)用以后,buffer的limit限制到了之前寫(xiě)入的position,position被設(shè)置成了0,mark也被重置了。通過(guò)這樣的方式可以讓數(shù)據(jù)position為0的地方開(kāi)始讀取,并且不會(huì)超出寫(xiě)入的數(shù)據(jù)上限。
remaining
public final int remaining() {return limit - position;}對(duì)比兩個(gè)變量的含義很容易就理解,remaining表示還有多少字節(jié)未讀取。
hasRemaining
public final boolean hasRemaining() {return position < limit;}是否還有未讀數(shù)據(jù)
clear
public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}雖然clear表面意思看起來(lái)像是清除數(shù)據(jù),但是實(shí)際上數(shù)據(jù)并沒(méi)有被清除,只是相關(guān)參數(shù)被重置了。相當(dāng)于buffer被創(chuàng)建的時(shí)候的初始狀態(tài),所以如果buffer中還存在未讀取的數(shù)據(jù),調(diào)用clear,那么數(shù)據(jù)就沒(méi)法讀取了。
compact
public ByteBuffer compact() {System.arraycopy(hb, ix(position()), hb, ix(0), remaining());position(remaining());limit(capacity());discardMark();return this;}簡(jiǎn)單看下就是把未讀取的數(shù)據(jù)copy到數(shù)據(jù)起始位置,外加把相關(guān)變量重置。compact和clear區(qū)別在于:如果存在未讀取的數(shù)據(jù),那么調(diào)用clear會(huì)無(wú)法再讀取數(shù)據(jù),compact則是把剩余未讀取數(shù)據(jù)復(fù)制到數(shù)組起始位置以便讀取。
allocate
public static ByteBuffer allocate(int capacity) {if (capacity < 0)throw new IllegalArgumentException();//調(diào)用 HeapByteBuffer 構(gòu)造函數(shù)return new HeapByteBuffer(capacity, capacity);}HeapByteBuffer(int cap, int lim) { // package-privatesuper(-1, 0, lim, cap, new byte[cap], 0);/*hb = new byte[cap];offset = 0;*/}ByteBuffer(int mark, int pos, int lim, int cap, // package-privatebyte[] hb, int offset){super(mark, pos, lim, cap);this.hb = hb;this.offset = offset;}Buffer(int mark, int pos, int lim, int cap) { // package-privateif (cap < 0)throw new IllegalArgumentException("Negative capacity: " + cap);this.capacity = cap;limit(lim);position(pos);if (mark >= 0) {if (mark > pos)throw new IllegalArgumentException("mark > position: ("+ mark + " > " + pos + ")");this.mark = mark;}}allocate方法創(chuàng)建了一個(gè)HeapByteBuffer實(shí)例。HeapByteBuffer是ByteBuffer的一個(gè)子類(lèi),在HeapByteBuffer的構(gòu)造器中調(diào)用了父類(lèi)ByteBuffer的構(gòu)造器,可以看到最終實(shí)際上是把ByteBuffer中每個(gè)變量賦值,同時(shí)我們也看到了在HeapByteBuffer中真正存放數(shù)據(jù)的是底層的byte數(shù)組。
allocateDirect
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);}// Primary constructor//DirectByteBuffer(int cap) { // package-privatesuper(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));Bits.reserveMemory(size, cap);long base = 0;try {base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;}allocateDirect 方法創(chuàng)建了一個(gè)DirectByteBuffer實(shí)例。DirectByteBuffer也是ByteBuffer的一個(gè)實(shí)例,和HeapByteBuffer的區(qū)別在于,HeapByteBuffer是指在JVM堆內(nèi)存中,而DirectByteBuffer是指直接內(nèi)存。通過(guò)看代碼可以看到,DirectByteBuffer通過(guò)unsafe.allocateMemory(size)直接申請(qǐng)內(nèi)存,通過(guò)unsafe.setMemory(base, size, (byte) 0);將申請(qǐng)的內(nèi)存數(shù)據(jù)清空。并且通過(guò)address變量維護(hù)指向改內(nèi)存的地址。
總結(jié):
Buffer在NIO中充當(dāng)著數(shù)據(jù)容器的角色,靈活的使用其非常重要。從源碼角度分析我們看到了,Buffer內(nèi)部通過(guò)維護(hù)不同的變量來(lái)實(shí)現(xiàn)讀寫(xiě)轉(zhuǎn)換,數(shù)據(jù)的存儲(chǔ)。所以理解這些關(guān)鍵變量(例如 postion mark limit capacity)基本就可以理解其所有api的使用邏輯以及場(chǎng)景。同時(shí)Buffer存在兩個(gè)重要的子類(lèi),一個(gè)是Heap,一個(gè)是Direct 分別在不同的場(chǎng)景里使用。
轉(zhuǎn)載于:https://www.cnblogs.com/coldridgeValley/p/6926335.html
總結(jié)
以上是生活随笔為你收集整理的[NIO系列]NIO源码分析之Buffer的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 关于树论【左偏树】
- 下一篇: UWP 显示图片到Image控件