緩衝區和通道

緩衝區

  • java.nio包提供了對緩衝區的支持,緩衝區是一種對象,表示存儲在內存中的數據流。
  • 緩衝區常被用來提高那些讀取輸入和發送輸出的程序的性能。它們讓程序能夠將大量的數據存儲到內存中,這樣使用和修改這些數據時速度將快很多。
  • 對於java的每種基本數據類型,都有相應的緩衝區:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer。

繼承關係

繼承關係圖
Buffer是頂層抽象類, ByteBuffer繼承Buffer,也是抽象類.
byteBuffer最常見的兩個具體實現類如下:DirectByteBuffer(JVM堆外部、通過unsafe.allocateMemory實現)、HeapByteBuffer(JVM堆)

常用函數

 int capacity() 
          返回此緩衝區的容量。 
Buffer flip() 
          反轉此緩衝區。 首先將限制設置爲當前位置,然後將位置設置爲 0。如果已定義了標
          記,則丟棄該標記。 
 Buffer position(int newPosition) 
          設置此緩衝區的位置。 
 Buffer position(int newPosition) 
          設置此緩衝區的位置。 
int position() 
          返回此緩衝區的位置。 
 int limit() 
          返回此緩衝區的限制。 
 Buffer limit(int newLimit) 
          設置此緩衝區的限制。 
 boolean hasRemaining() 
          告知在當前位置和限制之間是否有元素。 
 int remaining() 
          返回當前位置與限制之間的元素數。 

緩衝區的常見操作

  • 存取:get()、put()等操作,這些都是會自動改變position的。
  • 翻轉:也就是flip()操作。當要讀取數據的時候,需要將position置0,並將limit指針指向內容的最後面,也就是position置0之前的位置。等價於這個操作:buffer.limit(buffer.position()).position(0);所以,這個操作執行兩遍之後,它的position和limit是會變爲0,所以這個是不能再進行讀取或者寫入操作的。
  • 壓縮:可以釋放一部分的緩衝區空間。例如可以將position之前的位置釋放(position指針就是指向下次可以寫的位置),調用compact,就會將position到limit之間的數據拷貝到緩衝區0位置開始,然後將移動元素的個數作爲position的值(下次就可以從該位置接着寫了)。
  • 清除:就是將position置0和limit=容量大小。

緩衝區的四個屬性

  • 緩衝區的容量 :是它所包含的元素的數量。緩衝區的容量不能爲負並且不能更改。
  • 緩衝區的限制:是第一個不應該讀取或寫入的元素的索引。緩衝區的限制不能爲負,並且不能大於其容量。
  • 緩衝區的位置 是下一個要讀取或寫入的元素的索引。緩衝區的位置不能爲負,並且不能大於其限制。
  • 標記(mark):一個備忘位置,調用mark()方法的話,mark值將存儲當前position的值,等下次調用reset()方法時,會設定position的值爲之前的標記值;

示例觀察屬性值變化
寫一個輸出函數

public static void disProperty(ByteBuffer byteBuffer){
System.out.println("position  = "+byteBuffer.position ()+
" limit  = "+byteBuffer.limit ()+" capacity  = "+byteBuffer.capacity ());
	}

1、創建一個容量爲10的緩衝區

	ByteBuffer byteBuffer =ByteBuffer.allocate(10);

此時:mark = -1; position = 0; limit = 10; capacity = 10;
結果
2、往緩衝區輸入5個元素

		byteBuffer.put((byte) 'H');
		byteBuffer.put((byte) 'e');
		byteBuffer.put((byte) 'l');
		byteBuffer.put((byte) 'l');
		byteBuffer.put((byte) 'o');
等價於
byteBuffer.put((byte) 'H').put((byte) 'e').put((byte) 'l').put((byte) 'l').put((byte) 'o');
因爲這些函數的返回值也是byteBufer

此時:mark = -1; position = 5; limit = 10; capacity = 10;
結果
3、如果緩衝區存儲的數據量少於分配給他的內存空間(分配了10個字節,之存儲5個字節的數據),則將數據讀入到數據緩衝區後,應調用緩衝區的flip()函數,將limit指針指向內容的最後面,也就是position置0之前的位置。然後將位置設置爲 0。

		byteBuffer.flip();

此時:mark = -1; position = 0; limit = 5; capacity = 10;
結果
4、讀取兩個元素,輸出此時的position位置

	System.out.println((char)byteBuffer.get()+" "+(char)byteBuffer.get());	
	System.out.println(byteBuffer.position());

此時:mark = -1; position = 2; limit = 5; capacity = 10;
結果
5、標記此時的位置

byteBuffer.mark();

此時:mark = 2; position = 2; limit = 5; capacity = 10;
在這裏插入圖片描述
6、讀取兩個元素,恢復到之前mark的位置

	System.out.println((char)byteBuffer.get()+" "+(char)byteBuffer.get());
		byteBuffer.reset();

屬性變化情況
執行完第一行代碼之後:
在這裏插入圖片描述
執行完第二行代碼:mark = 2; position = 2; limit = 5; capacity = 10;
在這裏插入圖片描述
7、調用compact(),釋放已讀數據的空間,準備重新填充緩存區

byteBuffer.compact();

此時:mark = 2; position = 3; limit = 10; capacity = 10;
在這裏插入圖片描述
注意觀察數組中元素的變化,實際上進行了數組拷貝,拋棄了已讀字節元素,保留了未讀字節元素;

緩存之間的比較

這是equal的源碼:

public boolean equals(Object ob) {
        if (this == ob)
            return true;
        if (!(ob instanceof ByteBuffer))
            return false;
        ByteBuffer that = (ByteBuffer)ob;
        if (this.remaining() != that.remaining())
            return false;
        int p = this.position();
        for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)
            if (!equals(this.get(i), that.get(j)))
                return false;
        return true;
    }

總的來說,兩個緩衝區被認爲相等的條件如下=
(1)兩個對象類型相同。包含不同數據類型的 buffer 永遠不會相等,而且 buffer絕不會等於非 buffer 對象。
(2)兩個對象在當前位置和上屆之間的元素相同認爲是相同。
例如:

   	     int arr1[] = new int[]{1,2,3,4,5};
	     IntBuffer intBuffer1 = IntBuffer.wrap(arr1);
	     intBuffer1.position(1);
	     int arr2[] = new int[]{6,2,3,4,5};
	     IntBuffer intBuffer2 = IntBuffer.wrap(arr2);
	     intBuffer2.position(1);
	     System.out.println(intBuffer1.equals(intBuffer2));

輸出值爲true;

字符集

  • 字符集位於java.nio.charset包中,這些類用於在byte緩衝區和字符緩衝區之間進行數據轉換。主要的類有三個,分別是:
    (1)Charset:一個Unicode字符集,其中每個字符都有不同的byte值。
    (2)Decoder:將一系列的byte值轉換成一系列的字符。
    (3)Encoder:將一系列字符轉換成一系列字節值。
  • 在byte緩衝區和字符緩衝區之間進行轉換之前,必須創建一個Charset對象,它將字符映射到相應的byte值。
  • 要創建字符集,可調用Charset類的靜態方法forName(String),並將字符編碼技術的名稱作爲參數傳遞給它。常用的字符編碼方式有US-ASCII,ISO-8859-1,UTF-8,UTF-16BE,UTF-16LE,UTF-16。例如Charset charset= Charset.forName("ISO-8859-1");
  • 有了字符集對象之後,便可以使用它來創建編碼器和解碼器,要創建編碼器(CharsetDecoder)和解碼器(CharsetEncode),可調用字符集對象的方法newDecoder(),newEncoder()。例如CharsetDecoder charsetDecoder =charset.newDecoder(); CharsetEncoder charsetEncoder =charset.newEncoder();
  • 要將byte緩衝區轉換爲字符緩衝區,可調用解碼器的decode(ByteBuffer)方法,該方法返回一個CharBuffer,其中包含轉換得到的字符。
  • 要將字符緩衝區轉換成byte緩衝區,可調用編碼器的encode(CharBuffer)方法。該方法返回一個ByteBuffer,其中包含轉換得到的byte值。
    下面的語句使用ISO-8859-1字符集將一個名爲byteBuffer的byte緩衝區轉換成一個字符集。
	Charset charset =Charset.forName("ISO-8859-1");
	CharsetDecoder decoder =charset.newDecoder();
	byteBuffer.position(0);
	CharBuffer chBuffer =decoder.decode(byteBuffer);

warning: 使用解碼器來創建字符緩衝區之前,調用position()將byteBuffer的當前位置重置爲緩衝區的開頭,否則得到的緩衝區的內容可能比期望的少。

通道

  • 緩衝區常與輸入輸出流關聯起來,我們可以使用輸入流中的數據來填充緩衝區,或將緩衝區的數據寫入到輸出流。要實現這種操作,必須使用通道:一種將緩衝區和流連接起來的對象,通道類位於java.nio.channels包中。
  • 通道主要分爲兩大類,文件(File)通道和套接字(socket)通道,涉及的類有FileChannel類和三個socket通道類:SocketChannel、ServerSocketChannel和DatagramChannel;

FileChannel

  • FileChannel通道只能通過在一個打開的RandomAccessFile、FileInputStream或FileOutputStream對象上調用getChannel( )方法來獲取。
    例子爲從(UTF-8編碼的)文件讀取到字節流,並轉換成字符的程序
package ChannelDemo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

public class ChannelDemo {
	private static String FilePath ="C:/Users/asus-pc/Desktop/javaProject/Demo/src/ChannelDemo/test.txt";
	private static FileInputStream fileInputStream =null;
	private static FileChannel  fileChannel =null;
	public static void main(String[] args) {
	try {
		fileInputStream =new FileInputStream(FilePath);
		fileChannel = fileInputStream.getChannel();
		long size =fileChannel.size();
		ByteBuffer byteBuffer =ByteBuffer.allocate((int)size);
		fileChannel.read(byteBuffer);
		byteBuffer.position(0);
		System.out.println("未編碼前");
		while(byteBuffer.hasRemaining())
			System.out.print((char)byteBuffer.get());
		byteBuffer.position(0);
		Charset charset =Charset.forName("UTF-8");
		CharsetDecoder charsetDecoder =charset.newDecoder();
		CharBuffer charBuffer =charsetDecoder.decode(byteBuffer);
		System.out.println();
		charBuffer.position(1);
		System.out.println("UTF-8編碼後");
		while(charBuffer.hasRemaining())
			System.out.print(charBuffer.get());
	} catch (FileNotFoundException e) {
		
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章