首頁  >  文章  >  Java  >  Java NIO之緩衝區的程式碼圖文詳解

Java NIO之緩衝區的程式碼圖文詳解

黄舟
黄舟原創
2017-03-31 11:09:121684瀏覽

一個 Buffer 物件是固定數量的資料的容器。通道是 I/O 傳輸發生時通過的入口,而緩衝區是這些資料傳輸的來源或目標。

緩衝區基礎

所有的緩衝區都具有四個屬性來 供關於其所包含的資料元素的資訊。

  • capacity(容量):緩衝區能夠容納資料的最大值,在建立緩衝區後不能改變。

  • limit(上界):緩衝區的第一個不能被讀取或寫入的元素。或者,緩衝區現存元素的計數。

  • position(位置):下一個要被讀取或寫入的元素的索引。呼叫 get 或 put 函數更新

  • mark(標記):一個備忘位置。呼叫 mark() 來設定 mark=postion。呼叫 reset() 設定position= mark。標記在設定前是未定義的(undefined)。

這四個屬性之間總是循以下關係:

0 <= mark <= position <= limit <= capacity

下圖是一個新建立的ByteBuffer :

位置被設為0,而且容量和上界被設為10, 好經過緩衝區能夠容納的最後一個位元組。 標記最初未定義。容量是固定的,但另外的三個屬性可以在使用緩衝區時改變。

如下是 Buffer 的方法簽章:

public abstract class Buffer {

    public final int capacity() {
    }

    public final int position() {
    }

    public final Buffer position(int newPosition) {
    }

    public final int limit() {
    }

    public final Buffer limit(int newLimit) {
    }

    public final Buffer mark() {
    }

    public final Buffer reset() {
    }

    public final Buffer clear() {
    }

    public final Buffer flip() {
    }

    public final Buffer rewind() {
    }

    public final int remaining() {
    }

    public final boolean hasRemaining() {
    }

    public abstract boolean isReadOnly();

    public abstract boolean hasArray();

    public abstract Object array();

    public abstract int arrayOffset();

    public abstract boolean isDirect();
}

上文所列出的 Buffer API 並沒有包含 get() 或 put() 函式。每一個Buffer 類別都有這兩個函數,但它們所採用的參數類型,以及它們返回的資料型別,對每個子類別來說都是唯一的,所以它們不能在頂層Buffer 類中被抽像地聲明。

如下是ByteBuffer 的宣告:

public abstract class ByteBuffer extends Buffer implements Comparable
{
    // This is a partial API listing
    public abstract byte get( );
    public abstract byte get (int index);
    public abstract ByteBuffer put (byte b);
    public abstract ByteBuffer put (int index, byte b);
}

一個例子看ByteBuffer 的儲存:

buffer.put((byte)&#39;H&#39;).put((byte)&#39;e&#39;).put((byte)&#39;l&#39;).put((byte)&#39;l&#39;).put((byte)&#39;o&#39;);

##如果在進行如下操作:

buffer.put(0,(byte)&#39;M&#39;).put((byte)&#39;w&#39;);

當緩衝區寫滿了,要把內容讀出來,我們需要翻轉緩衝區,可以呼叫flip 方法,如下是ByteBuffer 當中的flip 方法:

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

rewind()函數與flip()相似,但不影響上界屬性。它只是將位置值設回 0。您可以使用 rewind()後 ,重讀已經被翻轉的緩衝區中的資料。

hasRemaining() 方法會傳回目前是否到達緩衝區的上界。

remaining() 方法會回到到達上界的距離。

緩衝區的標記在 mark() 函數被呼叫之前是未定義的,呼叫時標記被設為目前位置的值。

reset() 函數將位置設為目前的標記值。如果標記值未定義,呼叫 reset()將導致 InvalidMark

Exception 異常。

rewind( ),clear( ),以及 flip( )總是拋棄標記,即設定成 -1。

兩個緩衝區被認為相等的充要條件是:

  • 兩個物件類型相同。包含不同資料型別的buffer 遠不會相等,而且buffer 絕不等於非 buffer 物件。

  • 兩個物件都剩餘同樣數量的元素。 Buffer 的容量不需要相同,而且緩衝區中剩餘資料的索引也不必相同。但每個緩衝區中剩餘元素的數目(從位置到上界)必須相同。

  • 在每個緩衝區中應被Get()函數傳回的剩餘資料元素序列必須一致。

建立緩衝區

//分配一个容量为 100 个 char 变量的 Charbuffer
CharBuffer charBuffer = CharBuffer.allocate (100);

char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);
//这段代码构造了一个新的缓冲区对象,但数据元素会存在于数组中。
这意味着通过调用 put()函数造成的对缓冲区的改动会直接影响这个数组,而且对这个数组的任何改动也会对这 个缓冲区对象可见。

CharBuffer.wrap(array, offset, length);可以指定 position 和 length

透過allocate() 或wrap() 函式創建的緩衝區通常都是間接的,間接的緩衝區使用備份數組。 hasArray() 傳回這個緩衝區是否有一個可存取的備份陣列。

複製緩衝區

一些複製緩衝區的 api :

public abstract class CharBuffer extends Buffer implements CharSequence, Comparable {
	// This is a partial API listing
	public abstract CharBuffer duplicate( );
	public abstract CharBuffer asReadOnlyBuffer( );
	public abstract CharBuffer slice( );
}

duplicate() 函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元素,拥有同样的容量,但每个缓冲区拥有各自的位置,上界和标记属性。对一个缓冲区内的数据元素所做的改变会反映在另外一个缓冲区上。如果原始的缓冲区为只读,或者为直接缓冲区,新的缓冲区将继承这些属性。

如下示例:

public static void main(String[] args) {
		CharBuffer buffer = CharBuffer.allocate(8);
		buffer.position(3).limit(6).mark().position(5);
		CharBuffer dupeBuffer = buffer.duplicate();
		System.out.println(dupeBuffer.position());
		System.out.println(dupeBuffer.limit());
		dupeBuffer.clear();
		System.out.println(dupeBuffer.position());
		System.out.println(dupeBuffer.limit());
	}

//out
5
6
0
8

asReadOnlyBuffer() 函数来生成一个只读的缓冲区图。

代码说明如下:

public static void main(String[] args) {
		CharBuffer buffer = CharBuffer.allocate(8);
		CharBuffer dupeBuffer = buffer.asReadOnlyBuffer();
		System.out.println(dupeBuffer.isReadOnly());
		dupeBuffer.put(&#39;S&#39;);//只读buffer调用抛出异常
	}

//out
true
Exception in thread "main" java.nio.ReadOnlyBufferException
	at java.nio.HeapCharBufferR.put(HeapCharBufferR.java:172)
	at nio.test.TestMain.main(TestMain.java:10)

slice() 创建一个从原始缓冲区的当前位置开始的新缓冲 区,并且其容量是原始缓冲区的剩余元素数量(limit-position)。这个新缓冲区与原始 缓冲区共享一段数据元素子序列。分出来的缓冲区也会继承只读和直接属性。

public static void main(String[] args) {
		CharBuffer buffer = CharBuffer.allocate(8);
		buffer.position(3).limit(5);
		CharBuffer sliceBuffer = buffer.slice();
	}

字节缓冲区

在 java.nio 中,字节顺序由 ByteOrder 类封装。

ByteOrder.nativeOrder() 方法返回 JVM 运行的硬件平台字节顺序。

直接缓冲区

只有字节缓冲区有资格参与 I/O 操作。

I/O 操作的目标内存区域必须是连续的字节序列。

直接缓冲区被用于与通道和固有 I/O 例程交互。

直接字节缓冲区通常是 I/O 操作最好的选择。直接字节缓冲区支持 JVM 可用的最高效 I/O 机制。非直接字节缓冲区可以被传递给通道,但是这样可能导致性能耗。通常非直接缓冲不可能成为一个本地 I/O 操作的目标。如果向一个通道中传递一个非直接 ByteBuffer 对象用于写入会每次隐含调用下面的操作:

  1. 创建一个临时的直接 ByteBuffer 对象。

  2. 将非直接缓冲区的内容复制到直接临时缓冲中。

  3. 使用直接临时缓冲区执行低层次 I/O 操作。

  4. 直接临时缓冲区对象离开作用域,并最终成为被回的无用数据。

直接缓冲区时 I/O 的最佳选择,但可能比创建非直接缓冲区要花费更高的成本。直接缓冲区使用的内存是通过调用本地操作系统方面的代码分配的, 过了标准 JVM 。

ByteBuffer.allocateDirect() 创建直接缓冲区。isDirect() 返回是否直接缓冲区。

视图缓冲区

视图缓冲区通过已存在的缓冲区对象实例的工方法来创建。这种图对象维护它自己的属性,容量,位置,上界和标记,但是和原来的缓冲区共享数据元素。

ByteBuffer 类允许创建图来将 byte 型缓冲区字节数据映射为其它的原始数据类型。

public abstract CharBuffer asCharBuffer( );
public abstract ShortBuffer asShortBuffer( );
public abstract IntBuffer asIntBuffer( );
public abstract LongBuffer asLongBuffer( );
public abstract FloatBuffer asFloatBuffer( );
public abstract DoubleBuffer asDoubleBuffer( );

看如下一个例子的示意图:

ByteBuffer byteBuffer = ByteBuffer.allocate (7).order (ByteOrder.BIG_ENDIAN);
CharBuffer charBuffer = byteBuffer.asCharBuffer( );

public class BufferCharView {
       public static void main (String [] argv)  throws Exception  {
          ByteBuffer byteBuffer = ByteBuffer.allocate (7).order (ByteOrder.BIG_ENDIAN);
          CharBuffer charBuffer = byteBuffer.asCharBuffer( );
          // Load the ByteBuffer with some bytes
          byteBuffer.put (0, (byte)0);
          byteBuffer.put (1, (byte)&#39;H&#39;);
          byteBuffer.put (2, (byte)0);
          byteBuffer.put (3, (byte)&#39;i&#39;);
          byteBuffer.put (4, (byte)0);
          byteBuffer.put (5, (byte)&#39;!&#39;);
          byteBuffer.put (6, (byte)0);
          println (byteBuffer);
          println (charBuffer);
       }
       // Print info about a buffer
       private static void println (Buffer buffer)  {
		  System.out.println ("pos=" + buffer.position() + ", limit=" +
          	buffer.limit() + ", capacity=" + buffer.capacity() + ": &#39;" + buffer.toString( ) + "&#39;");
	}
}

//运行 BufferCharView 程序的输出是:
//pos=0, limit=7, capacity=7: &#39;java.nio.HeapByteBuffer[pos=0 lim=7 cap=7]&#39;
//pos=0, limit=3, capacity=3: &#39;Hi!

数据元素视图

ByteBuffer 类为每一种原始数据类型 供了存取的和转化的方法:

public abstract class ByteBuffer extends Buffer implements Comparable {
	public abstract char getChar();

	public abstract char getChar(int index);

	public abstract short getShort();

	public abstract short getShort(int index);

	public abstract int getInt();

	public abstract int getInt(int index);

	public abstract long getLong();

	public abstract long getLong(int index);

	public abstract float getFloat();

	public abstract float getFloat(int index);

	public abstract double getDouble();

	public abstract double getDouble(int index);

	public abstract ByteBuffer putChar(char value);

	public abstract ByteBuffer putChar(int index, char value);

	public abstract ByteBuffer putShort(short value);

	public abstract ByteBuffer putShort(int index, short value);

	public abstract ByteBuffer putInt(int value);

	public abstract ByteBuffer putInt(int index, int value);

	public abstract ByteBuffer putLong(long value);

	public abstract ByteBuffer putLong(int index, long value);

	public abstract ByteBuffer putFloat(float value);

	public abstract ByteBuffer putFloat(int index, float value);

	public abstract ByteBuffer putDouble(double value);

	public abstract ByteBuffer putDouble(int index, double value);
}

假如一个 bytebuffer 处于如下状态:

那么 int value = buffer.getInt();

实际的返回值取决于缓冲区的当前的比特排序(byte-order)设置。更具体的写法是:

int value = buffer.order (ByteOrder.BIG_ENDIAN).getInt( );

这将会返回值 0x3BC5315E,同时:

int value = buffer.order (ByteOrder.LITTLE_ENDIAN).getInt( );

返回值 0x5E31C53B

如果您试图获取的原始类型需要比缓冲区中存在的字节数更多的字节,会抛出 BufferUnderflowException。

以上是Java NIO之緩衝區的程式碼圖文詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn