[NIO系列]NIO源码分析之Buffer
在以前的一篇文章中我們介紹過IO模型
IO模型總結 http://www.cnblogs.com/coldridgeValley/p/5449758.html
,而在實際運用中多路復用IO使用很多,JDK早在1.4的時候就引入了NIO(new IO),今天我們來學習NIO基礎組件之一的Buffer的相關原理。
Java NIO由以下幾個核心部分組成:
- Buffer
- Channel
- Selector
傳統的IO操作是面向數據流的,這樣就意味著數據從流中讀取出來直到讀取完畢都是沒有任何地方可以緩存的。而NIO操作面向的是緩沖區,數據從Channel中讀取到Buffer緩沖區,在Buffer中處理數據,本文我們從源碼角度來解析緩沖區Buffer的代碼實現,從而理解其原理。
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文檔中對于Buffer的描述,簡單而言就是Buffer是一個線性的,有限容量的基本數據類型容器,對于所有的非布爾型變量,都有一種buffer可以與之對應。Buffer這個容器中有幾個重要的變量:
- capacity:表示buffer的容量,固定變量。
- limit:表示讀寫的邊界位置,讀寫的數據不會超過此值,改值永遠不大于capacity。
- position:表示下次讀寫開始的位置,大小永遠不大于limit。
引用一幅網上流傳非常廣泛的圖
看起來還是比較容易理解的。
Java NIO有如下常用的buffer類型:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- ShortBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
可以看到Buffer的類型代表不同的數據類型,也就是說Buffer中存放的數據可以是基本數據類型,MappedByteBuffer稍有不同,會單獨介紹。
我們在看Buffer的源碼之前先來看個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來讀寫數據基本包含四步:
- 數據寫入buffer
- 調用flip()將buffer轉換為讀模式
- 從buffer中讀取數據
- 調用buffer.clear()清空數據
ByteBuffer是我們最常用的Buffer之一,接下來我們從源碼角度一步一步來分析其內部一些重要方法的實現。
put
我們正常出了使用channel向buffer中寫入數據,還有其本身的put方法,put方法的重載版本很多。我們這里找最簡單的放入一個byte數據看下:
public ByteBuffer put(byte x) {hb[ix(nextPutIndex())] = x;return this;}final int nextPutIndex() { // package-privateif (position >= limit)throw new BufferOverflowException();return position++;}很簡單,把內部的position指針累加,同時把byte數據存放在內部的byte數組中。
mark
public final Buffer mark() {mark = position;return this;}mark變量是一個ByteBuffer特有的變量,mark()方法的目的就是將position值賦給mark變量從而達到記錄position的目的。
reset
public final Buffer reset() {int m = mark;if (m < 0)throw new InvalidMarkException();position = m;return this;}利用mark中記錄下來的position進行恢復,所以叫reset
flip
public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}flip的作用是將buffer的模式從寫模式轉換為讀模式,所以我們可以看到,通過flip調用以后,buffer的limit限制到了之前寫入的position,position被設置成了0,mark也被重置了。通過這樣的方式可以讓數據position為0的地方開始讀取,并且不會超出寫入的數據上限。
remaining
public final int remaining() {return limit - position;}對比兩個變量的含義很容易就理解,remaining表示還有多少字節未讀取。
hasRemaining
public final boolean hasRemaining() {return position < limit;}是否還有未讀數據
clear
public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}雖然clear表面意思看起來像是清除數據,但是實際上數據并沒有被清除,只是相關參數被重置了。相當于buffer被創建的時候的初始狀態,所以如果buffer中還存在未讀取的數據,調用clear,那么數據就沒法讀取了。
compact
public ByteBuffer compact() {System.arraycopy(hb, ix(position()), hb, ix(0), remaining());position(remaining());limit(capacity());discardMark();return this;}簡單看下就是把未讀取的數據copy到數據起始位置,外加把相關變量重置。compact和clear區別在于:如果存在未讀取的數據,那么調用clear會無法再讀取數據,compact則是把剩余未讀取數據復制到數組起始位置以便讀取。
allocate
public static ByteBuffer allocate(int capacity) {if (capacity < 0)throw new IllegalArgumentException();//調用 HeapByteBuffer 構造函數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方法創建了一個HeapByteBuffer實例。HeapByteBuffer是ByteBuffer的一個子類,在HeapByteBuffer的構造器中調用了父類ByteBuffer的構造器,可以看到最終實際上是把ByteBuffer中每個變量賦值,同時我們也看到了在HeapByteBuffer中真正存放數據的是底層的byte數組。
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 方法創建了一個DirectByteBuffer實例。DirectByteBuffer也是ByteBuffer的一個實例,和HeapByteBuffer的區別在于,HeapByteBuffer是指在JVM堆內存中,而DirectByteBuffer是指直接內存。通過看代碼可以看到,DirectByteBuffer通過unsafe.allocateMemory(size)直接申請內存,通過unsafe.setMemory(base, size, (byte) 0);將申請的內存數據清空。并且通過address變量維護指向改內存的地址。
總結:
Buffer在NIO中充當著數據容器的角色,靈活的使用其非常重要。從源碼角度分析我們看到了,Buffer內部通過維護不同的變量來實現讀寫轉換,數據的存儲。所以理解這些關鍵變量(例如 postion mark limit capacity)基本就可以理解其所有api的使用邏輯以及場景。同時Buffer存在兩個重要的子類,一個是Heap,一個是Direct 分別在不同的場景里使用。
轉載于:https://www.cnblogs.com/coldridgeValley/p/6926335.html
總結
以上是生活随笔為你收集整理的[NIO系列]NIO源码分析之Buffer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于树论【左偏树】
- 下一篇: UWP 显示图片到Image控件