1. 程式人生 > >Java中的緩衝區(直接緩衝區、非直接緩衝區等)

Java中的緩衝區(直接緩衝區、非直接緩衝區等)

  如果將同步I/O方式下的資料傳輸比做資料傳輸的零星方式(這裡的零星是指在資料傳輸的過程中是以零星的位元組方式進行的),那麼就可以將非阻塞I/O方式下的資料傳輸比做資料傳輸的集裝箱方式(在位元組和低層資料傳輸之間,多了一層緩衝區,因此,可以將緩衝區看做是裝載位元組的集裝箱)。大家可以想象,如果我們要運送比較少的貨物,用集裝箱好象有點不太合算,而如果要運送上百噸的貨物,用集裝箱來運送的成本會更低。在資料傳輸過程中也是一樣,如果資料量很小時,使用同步I/O方式會更適合,如果資料量很大時(一般以G為單位),使用非阻塞I/O方式的效率會更高。因此,從理論上說,資料量越大,使用非阻塞I/O方式的單位成本就會越低。產生這種結果的原因和緩衝區的一些特性有著直接的關係。在本節中,將對緩衝區的一些主要特性進行講解,使讀者可以充分理解緩衝區的概念,並能通過緩衝區來提高程式的執行效率

  建立緩衝區

  Java提供了七個基本的緩衝區,分別由七個類來管理,它們都可以在java.nio包中找到。這七個類如下所示:


    ByteBuffer 
  ShortBuffer 
  IntBuffer 
  CharBuffer 
  FloatBuffer 
  DoubleBuffer 
  LongBuffer 
  st1":*{behavior:url(#ieooui) }

  這七個類中的方法類似,只是它們的返回值或引數和相應的簡單型別相對應,如ByteBuffer類的get方法返回了byte型別的資料,而put方法需要一個byte型別的引數。在CharBuffer類中的get和put方法返回和傳遞的資料型別就是char。這七個類都沒有public構造方法,因此,它們不能通過new來建立相應的物件例項。這些類都可以通過兩種方式來建立相應的物件例項。

  1. 通過靜態方法allocate來建立緩衝區。

  這七類都有一個靜態的allocate方法,通過這個方法可以建立有最大容量限制的緩衝區物件。allocate的定義如下:

  ByteBuffer類中的allocate方法:


 public static ByteBuffer allocate(int capacity)

  IntBuffer類中的allocate方法:


 public static IntBuffer allocate(int capacity)

  其他五個緩衝區類中的allocate 方法定義和上面的定義類似,只是返回值的型別是相應的緩衝區類。

  allocate方法有一個引數capacity,用來指定緩衝區容量的最大值。capacity的不能小於0,否則會丟擲一個IllegalArgumentException異常。使用allocate來建立緩衝區,並不是一下子就分配給緩衝區capacity大小的空間,而是根據緩衝區中儲存資料的情況來動態分配緩衝區的大小(實際上,在低層Java採用了資料結構中的堆來管理緩衝區的大小),因此,這個capacity可以是一個很大的值,如1024*1024(1M)。allocate的使用方法如下:


 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); 
  IntBuffer intBuffer = IntBuffer.allocate(1024);

  在使用allocate建立緩衝區時應用注意,capacity的含義隨著緩衝區的不同而不同。如建立位元組緩衝區時,capacity指的是位元組數。而在建立整型(int)緩衝區時,capacity指的是int型值的數目,如果轉換成字數,capacity的值應該乘4。如上面程式碼中的intBuffer緩衝區最大可容納的位元組數是1024*4 = 4096個。

  2. 通過靜態方法wrap來建立緩衝區。

  使用allocate方法可以建立一個空的緩衝區。而wrap方法可以利用已經存在的資料來建立緩衝區。wrap方法可以將陣列直接轉換成相應型別的緩衝區。wrap方法有兩種過載形式,它們的定義如下:

  ByteBuffer類中的wrap方法:


    public static ByteBuffer wrap(byte[] array) 
  public static ByteBuffer wrap(byte[] array, int offset, int length)

  IntBuffer類中的wrap方法:


    public static IntBuffer wrap(byte[] array) 
  public static IntBuffer wrap(byte[] array, int offset, int length)

  其他五個緩衝區類中的wrap 方法定義和上面的定義類似,只是返回值的型別是相應的緩衝區類。

  在wrap方法中的array引數是要轉換的陣列(如果是其他的緩衝區類,陣列的型別就是相應的簡單型別,如IntBuffer類中的wrap方法的array就是int[]型別)。offset是要轉換的子陣列的偏移量,也就是子陣列在array中的開始索引。length是要轉換的子陣列的長度。利用後兩個引數可以將array陣列中的一部分轉換成緩衝區物件。它們的使用方法如下:


    byte[] myByte = new byte[] { 1, 2, 3 }; 
  int[] myInt = new int[] { 1, 2, 3, 4 }; 
  ByteBuffer byteBuffer = ByteBuffer.wrap(myByte); 
  IntBuffer intBuffer = IntBuffer.wrap(myInt, 1, 2);

  可以通過緩衝區類的capacity方法來得到緩衝區的大小。capacity方法的定義如下:


 public final int capacity()

  如果使用allocate方法來建立緩衝區,capacity方法的返回值就是capacity引數的值。而使用wrap方法來建立緩衝區,capacity方法的返回值是array陣列的長度,但要注意,使用wrap來轉換array的字陣列時,capacity的長度仍然是原陣列的長度,如上面程式碼中的intBuffer緩衝區的capacity值是4,而不是2。

  除了可以將陣列轉換成緩衝區外,也可以通過緩衝區類的array方法將緩衝區轉換成相應型別的陣列。IntBuffer類的array方法的定義方法如下(其他緩衝區類的array的定義類似):


 public final int[] array()

  下面的程式碼演示瞭如何使用array方法將緩衝區轉換成相應型別的陣列。


 int[] myInt = new int[] { 1, 2, 3, 4, 5, 6 }; 
  IntBuffer intBuffer = IntBuffer.wrap(myInt, 1, 3); 
  for (int v : intBuffer.array()) 
  System.out.print(v + " ");

  在執行上面程式碼後,我們發現輸出的結果是1 2 3 4 5 6,而不是2 3 4。這說明在將子陣列轉換成緩衝區的過程中實際上是將整個陣列轉換成了緩衝區,這就是用wrap包裝子陣列後,capacity的值仍然是原陣列長度的真正原因。在使用array方法時應注意,在以下兩種緩衝區中不能使用array方法:

  只讀的緩衝區

  如果使用只讀緩衝區的array方法,將會丟擲一個ReadOnlyBufferException異常。

  使用allocateDirect方法建立的緩衝區。

  如果呼叫這種緩衝區中的array方法,將會丟擲一個UnsupportedOperationException異常。

  可以通過緩衝區類的hasArray方法來判斷這個緩衝區是否可以使用array方法,如果返回true,則說明這個緩衝區可以使用array方法,否則,使用array方法將會丟擲上述的兩種異常之一。

  注意: 使用array方法返回的陣列並不是緩衝區資料的副本。被返回的陣列實際上就是緩衝區中的資料,也就是說,array方法只返回了緩衝區資料的引用。當陣列中的資料被修改後,緩衝區中的資料也會被修改,返之也是如此。關於這方面內容將在下一節“讀寫緩衝區中的資料”中詳細講解。

  在上述的七個緩衝區類中,ByteBuffer類和CharBuffer類各自還有另外一種方法來建立緩衝區物件。

  l ByteBuffer類

  可以通過ByteBuffer類的allocateDirect方法來建立ByteBuffer物件。allocateDirect方法的定義如下:


 public static ByteBuffer allocateDirect(int capacity)

  使用allocateDirect方法可以一次性分配capacity大小的連續位元組空間。通過allocateDirect方法來建立具有連續空間的ByteBuffer物件雖然可以在一定程度上提高效率,但這種方式並不是平臺獨立的。也就是說,在一些作業系統平臺上使用allocateDirect方法來建立ByteBuffer物件會使效率大幅度提高,而在另一些作業系統平臺上,效能會表現得非常差。而且allocateDirect方法需要較長的時間來分配記憶體空間,在釋放空間時也較慢。因此,在使用allocateDirect方法時應謹慎。

  通過isDirect方法可以判斷緩衝區物件(其他的緩衝區類也有isDirect方法,因為,ByteBuffer物件可以轉換成其他的緩衝區物件,這部分內容將在後面講解)是用哪種方式建立的,如果isDirect方法返回true,則這個緩衝區物件是用allocateDirect方法建立的,否則,就是用其他方法建立的緩衝區物件。

  l CharBuffer類

  我們可以發現,上述的七種緩衝區中並沒有字串緩衝區,而字串在程式中卻是最常用的一種資料型別。不過不要擔心,雖然java.nio包中並未提供字串緩衝區,但卻可以將字串轉換成字元緩衝區(就是CharBuffer物件)。在CharBuffer類中的wrap方法除了上述的兩種過載形式外,又多了兩種過載形式,它們的定義如下:


    public static CharBuffer wrap(CharSequence csq) 
  public static CharBuffer wrap(CharSequence csq, int start, int end)

  其中csq引數表示要轉換的字串,但我們注意到csq的型別並不是String,而是CharSequence。CharSequence類Java中四個可以表示字串的類的父類,這四個類是String、StringBuffer、StringBuilder和CharBuffer(大家要注意,StringBuffer和本節講的緩衝區類一點關係都沒有,這個類在java.lang包中)。也就是說,CharBuffer類的wrap方法可以將這四個類的物件轉換成CharBuffer物件。

  另外兩個引數start和end分別是子字串的開始索引和結束索引的下一個位置,如將字串"1234"中的"23" 轉換成CharBuffer物件的語句如下:


 CharBuffer cb = CharBuffer.wrap("1234", 1, 3);

  下面的程式碼演示瞭如何使用wrap方法將不同形式的字串轉換成CharBuffer物件。


    StringBuffer stringBuffer = new StringBuffer("通過StringBuffer建立CharBuffer物件"); 
  StringBuilder stringBuilder = new StringBuilder("通過StringBuilder建立CharBuffer物件"); 
  CharBuffer charBuffer1 = CharBuffer.wrap("通過String建立CharBuffer物件"); 
  CharBuffer charBuffer2 = CharBuffer.wrap(stringBuffer); 
  CharBuffer charBuffer3 = CharBuffer.wrap(stringBuilder); 
  CharBuffer charBuffer4 = CharBuffer.wrap(charBuffer1, 1, 3);

  本文出自 “軟體改變整個宇宙” 部落格,請務必保留此出處