1. 程式人生 > >java.nio.ByteBuffer 類 緩衝區

java.nio.ByteBuffer 類 緩衝區

一.

Buffer 類

定義了一個可以線性存放primitive type資料的容器介面。Buffer主要包含了與型別(byte, char…)無關的功能。
值得注意的是Buffer及其子類都不是執行緒安全的。

每個Buffer都有以下的屬性:

capacity
這個Buffer最多能放多少資料。capacity一般在buffer被建立的時候指定。

limit
在Buffer上進行的讀寫操作都不能越過這個下標。當寫資料到buffer中時,limit一般和capacity相等,當讀資料時,
limit代表buffer中有效資料的長度。

position
讀/寫操作的當前下標。當使用buffer的相對位置進行讀/寫操作時,讀/寫會從這個下標進行,並在操作完成後


buffer會更新下標的值。

mark
一個臨時存放的位置下標。呼叫mark()會將mark設為當前的position的值,以後呼叫reset()會將position屬性設
置為mark的值。mark的值總是小於等於position的值,如果將position的值設的比mark小,當前的
mark值會被拋棄掉。

這些屬性總是滿足以下條件:
0 <= mark <= position <= limit <= capacity

limit和position的值除了通過limit()和position()函式來設定,也可以通過下面這些函式來改變:

Buffer clear()

把position設為0,把limit設為capacity,一般在把資料寫入Buffer前呼叫。

Buffer flip()
把limit設為當前position,把position設為0,一般在從Buffer讀出資料前呼叫。

Buffer rewind()
把position設為0,limit不變,一般在把資料重寫入Buffer前呼叫。

Buffer物件有可能是隻讀的,這時,任何對該物件的寫操作都會觸發一個ReadOnlyBufferException。
isR
eadOnly()方法可以用來判斷一個Buffer是否只讀。

ByteBuffer 類

在Buffer的子類中,ByteBuffer是一個地位較為特殊的類,因為在java.io.channels中定義的各種chann

el的IO
操作基本上都是圍繞ByteBuffer展開的。

ByteBuffer定義了4個static方法來做建立工作:

ByteBuffer allocate(int capacity) //建立一個指定capacity的ByteBuffer。
ByteBuffer allocateDirect(int capacity) //建立一個direct的ByteBuffer,這樣的ByteBuffer在參與IO操作時效能會更好
ByteBuffer wrap(byte [] array)
ByteBuffer wrap(byte [] array, int offset, int length) //把一個byte陣列或byte陣列的一部分包裝成ByteBuffer。

ByteBuffer定義了一系列get和put操作來從中讀寫byte資料,如下面幾個:
byte get()
ByteBuffer get(byte [] dst)
byte get(int index)
ByteBuffer put(byte b)
ByteBuffer put(byte [] src)
ByteBuffer put(int index, byte b) 這些操作可分為絕對定位和相對定為兩種,相對定位的讀寫操作依靠position來定位Buffer中的位置,並在操
作完成後會更新position的值。在其它型別的buffer中,也定義了相同的函式來讀寫資料,唯一不同的就是一
些引數和返回值的型別。

除了讀寫byte型別資料的函式,ByteBuffer的一個特別之處是它還定義了讀寫其它primitive資料的方法,如:

int getInt()       //從ByteBuffer中讀出一個int值。
ByteBuffer putInt(int value)     // 寫入一個int值到ByteBuffer中。

讀寫其它型別的資料牽涉到位元組序問題,ByteBuffer會按其位元組序(大位元組序或小位元組序)寫入或讀出一個其

型別的資料(int,long…)。位元組序可以用order方法來取得和設定:
ByteOrder order() //返回ByteBuffer的位元組序。
ByteBuffer order(ByteOrder bo)   // 設定ByteBuffer的位元組序。

ByteBuffer另一個特別的地方是可以在它的基礎上得到其它型別的buffer。如:
CharBuffer asCharBuffer()
為當前的ByteBuffer建立一個CharBuffer的檢視。在該檢視buffer中的讀寫操作會按照ByteBuffer的位元組
序作用到ByteBuffer中的資料上。

用這類方法創建出來的buffer會從ByteBuffer的position位置開始到limit位置結束,可以看作是這段資料
的檢視。檢視buffer的readOnly屬性和direct屬性與ByteBuffer的一致,而且也只有通過這種方法,才可
以得到其他資料型別的direct buffer。

ByteOrder
用來表示ByteBuffer位元組序的類,可將其看成java中的enum型別。主要定義了下面幾個static方法和屬性:
ByteOrder BIG_ENDIAN       代表大位元組序的ByteOrder。
ByteOrder LITTLE_ENDIAN       代表小位元組序的ByteOrder。
ByteOrder nativeOrder()       返回當前硬體平臺的位元組序。

MappedByteBuffer
ByteBuffer的子類,是檔案內容在記憶體中的對映。這個類的例項需要通過FileChannel的map()方法來建立。

接下來看看一個使用ByteBuffer的例子,這個例子從標準輸入不停地讀入字元,當讀滿一行後,將收集的字元
寫到標準輸出:
public class ByteBufferTest {
	public static void main(String[] args) throws IOException {
		
		// 建立一個capacity為256的ByteBuffer
		ByteBuffer buf = ByteBuffer.allocate(256);
		while (true) {
			// 從標準輸入流讀入一個字元
			int c = System.in.read();
			// 當讀到輸入流結束時,退出迴圈
			if (c == -1)
				break;
			// 把讀入的字元寫入ByteBuffer中
			buf.put((byte) c);
			// 當讀完一行時,輸出收集的字元
			if (c == '\n') {
				// 呼叫flip()使limit變為當前的position的值,position變為0,
				// 為接下來從ByteBuffer讀取做準備
				buf.flip();
				// 構建一個byte陣列
				byte[] content = new byte[buf.limit()];
				// 從ByteBuffer中讀取資料到byte陣列中
				buf.get(content);
				// 把byte陣列的內容寫到標準輸出
				System.out.print(new String(content));
				// 呼叫clear()使position變為0,limit變為capacity的值,
				// 為接下來寫入資料到ByteBuffer中做準備
				buf.clear();
			}
		}
		
		
	}

}


二. 

類ByteBuffer是Java nio程式經常會用到的類,也是重要類 ,我們通過原始碼分析該類的實現原理。

一.ByteBuffer類的繼承結構

public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>

ByteBuffer的核心特性來自Buffer

二. ByteBuffer和Buffer的核心特性
A container for data of a specific primitive type. 用於特定基本型別資料的容器。
子類ByteBuffer支援除boolean型別以外的全部基本資料型別。

本質上,Buffer也就是由裝有特定基本型別資料的一塊記憶體緩衝區和操作資料的4個指標變數(mark標記,position位置, limit界限,capacity容量)組成。不多說,上原始碼:

public abstract class Buffer {

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
    ......
}

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>
{

    // These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    final byte[] hb;   // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;   // Valid only for heap buffers
    ......
}

其中,位元組陣列final byte[] hb就是所指的那塊記憶體緩衝區。

Buffer緩衝區的主要功能特性有:
a.Transferring data  資料傳輸,主要指可通過get()方法和put()方法向緩衝區存取資料,ByteBuffer提供存取除boolean以為的全部基本型別資料的方法。

b.Marking and resetting  做標記和重置,指mark()方法和reset()方法;而標記,無非是儲存操作中某個時刻的索引位置。

c.Invariants 各種指標變數

d.Clearing, flipping, and rewinding 清除資料,位置(position)置0(界限limit為當前位置),位置(position)置0(界限limit不變),指clear()方法, flip()方法和rewind()方法。

e.Read-only buffers 只讀緩衝區,指可將緩衝區設為只讀。

f.Thread safety 關於執行緒安全,指該緩衝區不是執行緒安全的,若多執行緒操作該緩衝區,則應通過同步來控制對該緩衝區的訪問。

g.Invocation chaining 呼叫鏈, 指該類的方法返回呼叫它們的緩衝區,因此,可將方法呼叫組成一個鏈;例如:
 b.flip();
 b.position(23);
 b.limit(42);
等同於
 b.flip().position(23).limit(42);

三.ByteBuffer的結構

ByteBuffer主要由是由裝資料的記憶體緩衝區和操作資料的4個指標變數(mark標記,position位置, limit界限,capacity容量)組成。
記憶體緩衝區:位元組陣列final byte[] hb;
ByteBuffer的主要功能也是由這兩部分配合實現的,如put()方法,就是向陣列byte[] hb存放資料。

    ByteBuffer bb = ByteBuffer.allocate(10); 
    // 向bb裝入byte資料
    bb.put((byte)9);


底層原始碼的實現如下

class HeapByteBuffer
    extends ByteBuffer
{
    ......
    public ByteBuffer put(byte x) {
      hb[ix(nextPutIndex())] = x;
      return this;
    }
    
    ......
    final int nextPutIndex() {    
      if (position >= limit)
      throw new BufferOverflowException();
       return position++;
    }
    ......
}


如上所述,bb.put((byte)9);執行時,先判斷position 是否超過 limit,否則指標position向前移一位,將位元組(byte)9存入position所指byte[] hb索引位置。

get()方法相似;

    public byte get() {
       return hb[ix(nextGetIndex())];
    }


4個指標的涵義

position:位置指標。微觀上,指向底層位元組陣列byte[] hb的某個索引位置;巨集觀上,是ByteBuffer的操作位置,如get()完成後,position指向當前(取出)元素的下一位,put()方法執行完成後,position指向當前(存入)元素的下一位;它是核心位置指標。

mark標記:儲存某個時刻的position指標的值,通過呼叫mark()實現;當mark被置為負值時,表示廢棄標記。

capacity容量:表示ByteBuffer的總長度/總容量,也即底層位元組陣列byte[] hb的容量,一般不可變,用於讀取。

limit界限:也是位置指標,表示待操作資料的界限,它總是和讀取或存入操作相關聯,limit指標可以被  改變,可以認為limit<=capacity。

ByteBuffer結構如下圖所示


四. ByteBuffer的關鍵方法實現

  1.取元素

    public abstract byte get();

    //HeapByteBuffer子類實現
    public byte get() {
       return hb[ix(nextGetIndex())];
    }


    //HeapByteBuffer子類方法
    final int nextGetIndex() {				
        if (position >= limit)
	    throw new BufferUnderflowException();
       return position++;
    } 


2.存元素

   public abstract ByteBuffer put(byte b);

    //HeapByteBuffer子類實現
    public ByteBuffer put(byte x) {
        hb[ix(nextPutIndex())] = x;
        return this;
    }


 3.清除資料   

 public final Buffer clear() {
     position = 0;
     limit = capacity;
     mark = -1;
     return this;
 }


 可見,對於clear()方法,ByteBuffer只是重置position指標和limit指標,廢棄mark標記,並沒有真正清空緩衝區/底層位元組陣列byte[] hb的資料;
    ByteBuffer也沒有提供真正清空緩衝區資料的介面,資料總是被覆蓋而不是清空。
    例如,對於Socket讀操作,若從socket中read到資料後,需要從頭開始存放到緩衝區,而不是從上次的位置開始繼續/連續存放,則需要clear(),重置position指標,但此時需要注意,若read到的資料沒有填滿緩衝區,則socket的read完成後,不能使用array()方法取出緩衝區的資料,因為array()返回的是整個緩衝區的資料,而不是上次read到的資料。

4. 以位元組陣列形式返回整個緩衝區的資料/byte[] hb的資料

    public final byte[] array() {
        if (hb == null)
	    throw new UnsupportedOperationException();
         if (isReadOnly)
            throw new ReadOnlyBufferException();
         return hb;
    }


  5.flip-位置重置

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }


socket的read操作完成後,若需要write剛才read到的資料,則需要在write執行前執行flip(),以重置操作位置指標,儲存操作資料的界限,保證write資料準確。

6.rewind-位置重置

   public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
   }


Rewinds this buffer. The position is set to zero and the mark is discarded.
  和flip()相比較而言,沒有執行limit = position;

7.判斷剩餘的操作資料或者剩餘的操作空間

    public final int remaining() {
        return limit - position;
    }


   常用於判斷socket的write操作中未寫出的資料;

 8.標記

    public final Buffer mark() {
        mark = position;
        return this;
    }


  9.重置到標記

    public final Buffer reset() {
        int m = mark;
        if (m < 0)
          throw new InvalidMarkException();
        position = m;
        return this;
    }


五.建立ByteBuffer物件的方式

   1.allocate方式

    public static ByteBuffer allocate(int capacity) {
      if (capacity < 0)
          throw new IllegalArgumentException();
          return new HeapByteBuffer(capacity, capacity);
    }

    HeapByteBuffer(int cap, int lim) {  // package-private
         super(-1, 0, lim, cap, new byte[cap], 0);
         /*
         hb = new byte[cap];
         offset = 0;
         */
    }


    // Creates a new buffer with the given mark, position, limit, capacity,
    // backing array, and array offset
    //
    ByteBuffer(int mark, int pos, int lim, int cap, // package-private
            byte[] hb, int offset)
    {
      super(mark, pos, lim, cap);
      this.hb = hb;
      this.offset = offset;
    }


    // Creates a new buffer with the given mark, position, limit, and capacity,
    // after checking invariants.
    //
    Buffer(int mark, int pos, int lim, int cap) { // package-private
        if (cap < 0)
          throw new IllegalArgumentException();
         this.capacity = cap;
         limit(lim);
         position(pos);
         if (mark >= 0) {
           if (mark > pos)
              throw new IllegalArgumentException();
           this.mark = mark;
         }
     }
 


 由此可見,allocate方式建立ByteBuffer物件的主要工作包括: 新建底層位元組陣列byte[] hb(長度為capacity),mark置為-1,position置為0,limit置為capacity,capacity為使用者指定的長度。

   2.wrap方式

 public static ByteBuffer wrap(byte[] array) {
	return wrap(array, 0, array.length);
    }


    public static ByteBuffer wrap(byte[] array,
				    int offset, int length)
    {
        try {
           return new HeapByteBuffer(array, offset, length);
        } catch (IllegalArgumentException x) {
           throw new IndexOutOfBoundsException();
        }
    }

    HeapByteBuffer(byte[] buf, int off, int len) { // package-private
         super(-1, off, off + len, buf.length, buf, 0);
        /*
        hb = buf;
        offset = 0;
        */
     }


 wrap方式和allocate方式本質相同,不過因為由使用者指定的引數不同,引數為byte[] array,所以不需要新建位元組陣列,byte[] hb置為byte[] array,mark置為-1,position置為0,limit置為array.length,capacity置為array.length。

   六、結論

        由此可見,ByteBuffer的底層結構清晰,不復雜,原始碼仍是弄清原理的最佳文件。
讀完此文,應該當Java nio的SocketChannel進行read或者write操作時,ByteBuffer的四個指標如何移動有了清晰的認識。