2.JAVA NIO緩衝區

第二章 緩衝區

我們以 Buffer 類開始我們對 java.nio 軟件包的瀏覽歷程。這些類是 java.nio 的構 造基礎。在本章中,我們將深入研究緩衝區,瞭解各種不同的類型,並學會怎樣使用。到那時 我們將明瞭 java.nio 緩衝區是如何與 java.nio.channels 這一通道類相聯繫的。

一個Buffer對象是固定數量的數據的容器。其作用是一個存儲器,或者分段運輸區,在 這裏數據可被存儲並在之後用於檢索。緩衝區如我們在第一章所討論的那樣被寫滿和釋放。對 於每個非布爾原始數據類型都有一個緩衝區類。儘管緩衝區作用於它們存儲的原始數據類型, 但緩衝區十分傾向於處理字節。非字節緩衝區可以在後臺執行從字節或到字節的轉換,這取決 於緩衝區是如何創建的 2 。我們將在本章節後面的部分檢查緩衝區數據存儲的含義。

緩衝區的工作與通道緊密聯繫。通道是 I/O 傳輸發生時通過的入口,而緩衝區是這些數 據傳輸的來源或目標。對於離開緩衝區的傳輸,您想傳遞出去的數據被置於一個緩衝區,被傳 送到通道。對於傳回緩衝區的傳輸,一個通道將數據放置在您所提供的緩衝區中。這種在協同 對象(通常是您所寫的對象以及一到多個 Channel 對象)之間進行的緩衝區數據傳遞是高效 數據處理的關鍵。通道將在第三章被詳細涉及。

圖 2-1 是 Buffer 的類層次圖。在頂部是通用 Buffer 類。Buffer 定義所有緩衝區類 型共有的操作,無論是它們所包含的數據類型還是可能具有的特定行爲。這一共同點將會成爲 我們的出發點。

 

/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.nio;

import java.util.Spliterator;

/**
 * A container for data of a specific primitive type.
 *
 * <p> 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: </p>
 *
 * <blockquote>
 *
 *   <p> A buffer's <i>capacity</i> is the number of elements it contains.  The
 *   capacity of a buffer is never negative and never changes.  </p>
 *
 *   <p> A buffer's <i>limit</i> 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.  </p>
 *
 *   <p> A buffer's <i>position</i> 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.  </p>
 *
 * </blockquote>
 *
 * <p> There is one subclass of this class for each non-boolean primitive type.
 *
 *
 * <h2> Transferring data </h2>
 *
 * <p> Each subclass of this class defines two categories of <i>get</i> and
 * <i>put</i> operations: </p>
 *
 * <blockquote>
 *
 *   <p> <i>Relative</i> operations read or write one or more elements starting
 *   at the current position and then increment the position by the number of
 *   elements transferred.  If the requested transfer exceeds the limit then a
 *   relative <i>get</i> operation throws a {@link BufferUnderflowException}
 *   and a relative <i>put</i> operation throws a {@link
 *   BufferOverflowException}; in either case, no data is transferred.  </p>
 *
 *   <p> <i>Absolute</i> operations take an explicit element index and do not
 *   affect the position.  Absolute <i>get</i> and <i>put</i> operations throw
 *   an {@link IndexOutOfBoundsException} if the index argument exceeds the
 *   limit.  </p>
 *
 * </blockquote>
 *
 * <p> Data may also, of course, be transferred in to or out of a buffer by the
 * I/O operations of an appropriate channel, which are always relative to the
 * current position.
 *
 *
 * <h2> Marking and resetting </h2>
 *
 * <p> A buffer's <i>mark</i> is the index to which its position will be reset
 * when the {@link #reset reset} method is invoked.  The mark is not always
 * defined, but when it is defined it is never negative and is never greater
 * than the position.  If the mark is defined then it is discarded when the
 * position or the limit is adjusted to a value smaller than the mark.  If the
 * mark is not defined then invoking the {@link #reset reset} method causes an
 * {@link InvalidMarkException} to be thrown.
 *
 *
 * <h2> Invariants </h2>
 *
 * <p> The following invariant holds for the mark, position, limit, and
 * capacity values:
 *
 * <blockquote>
 *     <tt>0</tt> <tt>&lt;=</tt>
 *     <i>mark</i> <tt>&lt;=</tt>
 *     <i>position</i> <tt>&lt;=</tt>
 *     <i>limit</i> <tt>&lt;=</tt>
 *     <i>capacity</i>
 * </blockquote>
 *
 * <p> A newly-created buffer always has a position of zero and a mark that is
 * undefined.  The initial limit may be zero, or it may be some other value
 * that depends upon the type of the buffer and the manner in which it is
 * constructed.  Each element of a newly-allocated buffer is initialized
 * to zero.
 *
 *
 * <h2> Clearing, flipping, and rewinding </h2>
 *
 * <p> In addition to methods for accessing the position, limit, and capacity
 * values and for marking and resetting, this class also defines the following
 * operations upon buffers:
 *
 * <ul>
 *
 *   <li><p> {@link #clear} makes a buffer ready for a new sequence of
 *   channel-read or relative <i>put</i> operations: It sets the limit to the
 *   capacity and the position to zero.  </p></li>
 *
 *   <li><p> {@link #flip} makes a buffer ready for a new sequence of
 *   channel-write or relative <i>get</i> operations: It sets the limit to the
 *   current position and then sets the position to zero.  </p></li>
 *
 *   <li><p> {@link #rewind} makes a buffer ready for re-reading the data that
 *   it already contains: It leaves the limit unchanged and sets the position
 *   to zero.  </p></li>
 *
 * </ul>
 *
 *
 * <h2> Read-only buffers </h2>
 *
 * <p> Every buffer is readable, but not every buffer is writable.  The
 * mutation methods of each buffer class are specified as <i>optional
 * operations</i> that will throw a {@link ReadOnlyBufferException} when
 * invoked upon a read-only buffer.  A read-only buffer does not allow its
 * content to be changed, but its mark, position, and limit values are mutable.
 * Whether or not a buffer is read-only may be determined by invoking its
 * {@link #isReadOnly isReadOnly} method.
 *
 *
 * <h2> Thread safety </h2>
 *
 * <p> Buffers are not safe for use by multiple concurrent threads.  If a
 * buffer is to be used by more than one thread then access to the buffer
 * should be controlled by appropriate synchronization.
 *
 *
 * <h2> Invocation chaining </h2>
 *
 * <p> Methods in this class that do not otherwise have a value to return are
 * specified to return the buffer upon which they are invoked.  This allows
 * method invocations to be chained; for example, the sequence of statements
 *
 * <blockquote><pre>
 * b.flip();
 * b.position(23);
 * b.limit(42);</pre></blockquote>
 *
 * can be replaced by the single, more compact statement
 *
 * <blockquote><pre>
 * b.flip().position(23).limit(42);</pre></blockquote>
 *
 *
 * @author Mark Reinhold
 * @author JSR-51 Expert Group
 * @since 1.4
 */

public abstract class Buffer {

    /**
     * The characteristics of Spliterators that traverse and split elements
     * maintained in Buffers.
     */
    static final int SPLITERATOR_CHARACTERISTICS =
        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

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

    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;

    // 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("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;
        }
    }

    /**
     * Returns this buffer's capacity.
     *
     * @return  The capacity of this buffer
     */
    public final int capacity() {
        return capacity;
    }

    /**
     * Returns this buffer's position.
     *
     * @return  The position of this buffer
     */
    public final int position() {
        return position;
    }

    /**
     * Sets this buffer's position.  If the mark is defined and larger than the
     * new position then it is discarded.
     *
     * @param  newPosition
     *         The new position value; must be non-negative
     *         and no larger than the current limit
     *
     * @return  This buffer
     *
     * @throws  IllegalArgumentException
     *          If the preconditions on <tt>newPosition</tt> do not hold
     */
    public final Buffer position(int newPosition) {
        if ((newPosition > limit) || (newPosition < 0))
            throw new IllegalArgumentException();
        position = newPosition;
        if (mark > position) mark = -1;
        return this;
    }

    /**
     * Returns this buffer's limit.
     *
     * @return  The limit of this buffer
     */
    public final int limit() {
        return limit;
    }

    /**
     * Sets this buffer's limit.  If the position is larger than the new limit
     * then it is set to the new limit.  If the mark is defined and larger than
     * the new limit then it is discarded.
     *
     * @param  newLimit
     *         The new limit value; must be non-negative
     *         and no larger than this buffer's capacity
     *
     * @return  This buffer
     *
     * @throws  IllegalArgumentException
     *          If the preconditions on <tt>newLimit</tt> do not hold
     */
    public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        limit = newLimit;
        if (position > limit) position = limit;
        if (mark > limit) mark = -1;
        return this;
    }

    /**
     * Sets this buffer's mark at its position.
     *
     * @return  This buffer
     */
    public final Buffer mark() {
        mark = position;
        return this;
    }

    /**
     * Resets this buffer's position to the previously-marked position.
     *
     * <p> Invoking this method neither changes nor discards the mark's
     * value. </p>
     *
     * @return  This buffer
     *
     * @throws  InvalidMarkException
     *          If the mark has not been set
     */
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

    /**
     * Clears this buffer.  The position is set to zero, the limit is set to
     * the capacity, and the mark is discarded.
     *
     * <p> Invoke this method before using a sequence of channel-read or
     * <i>put</i> operations to fill this buffer.  For example:
     *
     * <blockquote><pre>
     * buf.clear();     // Prepare buffer for reading
     * in.read(buf);    // Read data</pre></blockquote>
     *
     * <p> This method does not actually erase the data in the buffer, but it
     * is named as if it did because it will most often be used in situations
     * in which that might as well be the case. </p>
     *
     * @return  This buffer
     */
    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

    /**
     * Flips this buffer.  The limit is set to the current position and then
     * the position is set to zero.  If the mark is defined then it is
     * discarded.
     *
     * <p> After a sequence of channel-read or <i>put</i> operations, invoke
     * this method to prepare for a sequence of channel-write or relative
     * <i>get</i> operations.  For example:
     *
     * <blockquote><pre>
     * buf.put(magic);    // Prepend header
     * in.read(buf);      // Read data into rest of buffer
     * buf.flip();        // Flip buffer
     * out.write(buf);    // Write header + data to channel</pre></blockquote>
     *
     * <p> This method is often used in conjunction with the {@link
     * java.nio.ByteBuffer#compact compact} method when transferring data from
     * one place to another.  </p>
     *
     * @return  This buffer
     */
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

    /**
     * Rewinds this buffer.  The position is set to zero and the mark is
     * discarded.
     *
     * <p> Invoke this method before a sequence of channel-write or <i>get</i>
     * operations, assuming that the limit has already been set
     * appropriately.  For example:
     *
     * <blockquote><pre>
     * out.write(buf);    // Write remaining data
     * buf.rewind();      // Rewind buffer
     * buf.get(array);    // Copy data into array</pre></blockquote>
     *
     * @return  This buffer
     */
    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

    /**
     * Returns the number of elements between the current position and the
     * limit.
     *
     * @return  The number of elements remaining in this buffer
     */
    public final int remaining() {
        return limit - position;
    }

    /**
     * Tells whether there are any elements between the current position and
     * the limit.
     *
     * @return  <tt>true</tt> if, and only if, there is at least one element
     *          remaining in this buffer
     */
    public final boolean hasRemaining() {
        return position < limit;
    }

    /**
     * Tells whether or not this buffer is read-only.
     *
     * @return  <tt>true</tt> if, and only if, this buffer is read-only
     */
    public abstract boolean isReadOnly();

    /**
     * Tells whether or not this buffer is backed by an accessible
     * array.
     *
     * <p> If this method returns <tt>true</tt> then the {@link #array() array}
     * and {@link #arrayOffset() arrayOffset} methods may safely be invoked.
     * </p>
     *
     * @return  <tt>true</tt> if, and only if, this buffer
     *          is backed by an array and is not read-only
     *
     * @since 1.6
     */
    public abstract boolean hasArray();

    /**
     * Returns the array that backs this
     * buffer&nbsp;&nbsp;<i>(optional operation)</i>.
     *
     * <p> This method is intended to allow array-backed buffers to be
     * passed to native code more efficiently. Concrete subclasses
     * provide more strongly-typed return values for this method.
     *
     * <p> Modifications to this buffer's content will cause the returned
     * array's content to be modified, and vice versa.
     *
     * <p> Invoke the {@link #hasArray hasArray} method before invoking this
     * method in order to ensure that this buffer has an accessible backing
     * array.  </p>
     *
     * @return  The array that backs this buffer
     *
     * @throws  ReadOnlyBufferException
     *          If this buffer is backed by an array but is read-only
     *
     * @throws  UnsupportedOperationException
     *          If this buffer is not backed by an accessible array
     *
     * @since 1.6
     */
    public abstract Object array();

    /**
     * Returns the offset within this buffer's backing array of the first
     * element of the buffer&nbsp;&nbsp;<i>(optional operation)</i>.
     *
     * <p> If this buffer is backed by an array then buffer position <i>p</i>
     * corresponds to array index <i>p</i>&nbsp;+&nbsp;<tt>arrayOffset()</tt>.
     *
     * <p> Invoke the {@link #hasArray hasArray} method before invoking this
     * method in order to ensure that this buffer has an accessible backing
     * array.  </p>
     *
     * @return  The offset within this buffer's array
     *          of the first element of the buffer
     *
     * @throws  ReadOnlyBufferException
     *          If this buffer is backed by an array but is read-only
     *
     * @throws  UnsupportedOperationException
     *          If this buffer is not backed by an accessible array
     *
     * @since 1.6
     */
    public abstract int arrayOffset();

    /**
     * Tells whether or not this buffer is
     * <a href="ByteBuffer.html#direct"><i>direct</i></a>.
     *
     * @return  <tt>true</tt> if, and only if, this buffer is direct
     *
     * @since 1.6
     */
    public abstract boolean isDirect();


    // -- Package-private methods for bounds checking, etc. --

    /**
     * Checks the current position against the limit, throwing a {@link
     * BufferUnderflowException} if it is not smaller than the limit, and then
     * increments the position.
     *
     * @return  The current position value, before it is incremented
     */
    final int nextGetIndex() {                          // package-private
        if (position >= limit)
            throw new BufferUnderflowException();
        return position++;
    }

    final int nextGetIndex(int nb) {                    // package-private
        if (limit - position < nb)
            throw new BufferUnderflowException();
        int p = position;
        position += nb;
        return p;
    }

    /**
     * Checks the current position against the limit, throwing a {@link
     * BufferOverflowException} if it is not smaller than the limit, and then
     * increments the position.
     *
     * @return  The current position value, before it is incremented
     */
    final int nextPutIndex() {                          // package-private
        if (position >= limit)
            throw new BufferOverflowException();
        return position++;
    }

    final int nextPutIndex(int nb) {                    // package-private
        if (limit - position < nb)
            throw new BufferOverflowException();
        int p = position;
        position += nb;
        return p;
    }

    /**
     * Checks the given index against the limit, throwing an {@link
     * IndexOutOfBoundsException} if it is not smaller than the limit
     * or is smaller than zero.
     */
    final int checkIndex(int i) {                       // package-private
        if ((i < 0) || (i >= limit))
            throw new IndexOutOfBoundsException();
        return i;
    }

    final int checkIndex(int i, int nb) {               // package-private
        if ((i < 0) || (nb > limit - i))
            throw new IndexOutOfBoundsException();
        return i;
    }

    final int markValue() {                             // package-private
        return mark;
    }

    final void truncate() {                             // package-private
        mark = -1;
        position = 0;
        limit = 0;
        capacity = 0;
    }

    final void discardMark() {                          // package-private
        mark = -1;
    }

    static void checkBounds(int off, int len, int size) { // package-private
        if ((off | len | (off + len) | (size - (off + len))) < 0)
            throw new IndexOutOfBoundsException();
    }

}

1.緩衝區基礎

概念上,緩衝區是包在一個對象內的基本數據元素數組。Buffer 類相比一個簡單數組的優點是它將關於數據的數據內容和信息包含在一個單一的對象中。Buffer 類以及它專有的子類定義了一個用於處理數據緩衝區的 API。

1)屬性

所有的緩衝區都具有四個屬性來提供關於其所包含的數據元素的信息。

    // 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("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;
        }
    }
  • 容量(Capacity)

    • 緩衝區能夠容納的數據元素的最大數量。這一容量在緩衝區創建時被設定,並且永遠不能 被改變。

  • 上界(Limit)

    • 緩衝區的第一個不能被讀或寫的元素。或者說,緩衝區中現存元素的計數。

  • 位置(Position)

    • 下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新。

  • 標記(Mark)

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

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

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

讓我們來看看這些屬性在實際應用中的一些例子。圖 2-2 展示了一個新創建的容量爲 10 的 ByteBuffer 邏輯視圖。

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

2)緩衝區API

讓我們來看一下可以如何使用一個緩衝區。以下是 Buffer 類的方法簽名:

package java.nio;

public abstract class Buffer {

    Buffer(int mark, int pos, int lim, int cap)
    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();
}

關於這個 API 有一點要注意的是,像 clear()這類函數,您通常應當返回 void,而不 是 Buffer 引用。這些函數將引用返回到它們在(this)上被引用的對象。這是一個允許級 聯調用的類設計方法。級聯調用允許這種類型的代碼:

buffer.mark( ); 
buffer.position(5); 
buffer.reset( );

  被簡寫爲:

buffer.mark().position(5).reset();

java.nio 中的類被特意地設計爲支持級聯調用。您可能已經在 StringBuffer 類中看 到了級聯調用的使用。

對於 API 還要注意的一點是 isReadOnly()函數。所有的緩衝區都是可讀的,但並非所 有都可寫。每個具體的緩衝區類都通過執行 isReadOnly()來標示其是否允許該緩存區的內 容被修改。一些類型的緩衝區類可能未使其數據元素存儲在一個數組中。例如 MappedByteBuffer 的內容可能實際是一個只讀文件。您也可以明確地創建一個只讀視圖緩 衝區,來防止對內容的意外修改。對只讀的緩衝區的修改嘗試將會導致 ReadOnlyBufferException 拋出。但是我們要提前做好準備。

3)存取

讓我們從起點開始。緩衝區管理着固定數目的數據元素。但在任何特定的時刻,我們可能只對緩衝區中的一部分元素感興趣。換句話說,在我們想清空緩衝區之前,我們可能只使用了緩衝區的一部分。這時,我們需要能夠追蹤添加到緩衝區內的數據元素的數量,放入下一個元素的位置等等的方法。位置屬性做到了這一點。它在調用 put()時指出了下一個數據元素應該被插入的位置,或者當get()被調用時指出下一個元素應從何處檢索。聰明的讀者會注意到上文所列出的Buffer API 並沒有包括 get()或 put()函數。每一個 Buffer 類都有這 兩個函數,但它們所採用的參數類型,以及它們返回的數據類型,對每個子類來說都是唯一 的,所以它們不能在頂層 Buffer 類中被抽象地聲明。它們的定義必須被特定類型的子類所遵從。 對於這一討論,我們將假設使用具有這裏所給出的函數的 ByteBuffer 類(get()和 put()還有更多的形式,我們將在 2.1.10 小節中進行討論):

package java.nio;

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

    // 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)

    // Creates a new buffer with the given mark, position, limit, and capacity
    //
    ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
        this(mark, pos, lim, cap, null, 0);
    }

    public abstract byte get();

    public abstract ByteBuffer put(byte b);

    public abstract byte get(int index);

    public abstract ByteBuffer put(int index, byte b);
}

Get 和 put 可以是相對的或者是絕對的。在前面的程序列表中,相對方案是不帶有索引 參數的函數。當相對函數被調用時,位置在返回時前進一。如果位置前進過多,相對運算就會 拋 出 異 常 。 對 於 put() , 如 果 運 算 會 導 致 位 置 超 出 上 界 , 就 會 拋 出 BufferOverflowException 異常 。 對 於 get() , 如 果 位置 不 小於上 界 , 就 會 拋出 BufferUnderflowException 異常。絕對存取不會影響緩衝區的位置屬性,但是如果您所 提供的索引超出範圍(負數或不小於上界),也將拋出 IndexOutOfBoundsException 異 常。

4)填充

讓我們看一個例子。我們將代表“Hello”字符串的 ASCII 碼載入一個名爲 buffer 的 ByteBuffer 對象中。當在圖 2.2 所新建的緩衝區上執行以下代碼後,緩衝區的結果狀態如 圖 2.3 所示:

buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');

注意本例中的每個字符都必須被強制轉換爲 byte。我們不能不經強制轉換而這樣操做:

buffer.put('H');

因爲我們存放的是字節而不是字符。記住在 java 中,字符在內部以 Unicode 碼錶示, 每個 Unicode 字符佔 16 位。本章節的例子使用包含 ascii 字符集數值的字節。通過將 char 強制轉換爲 byte,我們刪除了前八位來建立一個八位字節值。這通常只適合於拉丁字 符而不能適合所有可能的 Unicode 字符。爲了讓事情簡化,我們暫時故意忽略字符集的映射 問題。第六章中將詳細涉及字符編碼。

既然我們已經在 buffer 中存放了一些數據,如果我們想在不丟失位置的情況下進行一些 更改該怎麼辦呢?put()的絕對方案可以達到這樣的目的。 假設我們想將緩衝區中的內容從 “Hello”的 ASCII 碼更改爲“Mellow”。我們可以這樣實現:

buffer.put(0,(byte)'M').put((byte)'w');

這裏通過進行一次絕對方案的 put 將 0 位置的字節代替爲十六進制數值 0x4d,將 0x77 放入當前位置(當前位置不會受到絕對 put()的影響)的字節,並將位置屬性加一。結果如 圖 2.4 所示。

5)翻轉

我們已經寫滿了緩衝區,現在我們必須準備將其清空。我們想把這個緩衝區傳遞給一個通 道,以使內容能被全部寫出。但如果通道現在在緩衝區上執行 get(),那麼它將從我們剛剛 插入的有用數據之外取出未定義數據。如果我們將位置值重新設爲 0,通道就會從正確位置開 始獲取,但是它是怎樣知道何時到達我們所插入數據末端的呢?這就是上界屬性被引入的目 的。上界屬性指明瞭緩衝區有效內容的末端。我們需要將上界屬性設置爲當前位置,然後將位 置重置爲 0。我們可以人工用下面的代碼實現:

buffer.limit(buffer.position()).position(0);

但這種從填充到釋放狀態的緩衝區翻轉是 API 設計者預先設計好的,他們爲我們提供了 一個非常便利的函數:

Buffer.flip();

Flip()函數將一個能夠繼續添加數據元素的填充狀態的緩衝區翻轉成一個準備讀出元素 的釋放狀態。在翻轉之後,圖 2.4 的緩衝區會變成圖 2.5 中的樣子。

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

如果將緩衝區翻轉兩次會怎樣呢?它實際上會大小變爲 0。按照圖 2.5 的相同步驟對緩衝 區進行操作;把上界設爲位置的值,並把位置設爲 0。上界和位置都變成 0。嘗試對緩衝區上 位置和上界都爲 0 的 get()操作會導致 BufferUnderflowException 異常。而 put()則 會導致 BufferOverflowException 異常。

6)釋放

如果我們現在將圖 2.5 中的緩衝區傳入通道,它將取出我們存放在那裏的數據,從位置 開始直到上界結束。很簡單,不是嗎?

同樣地, 如果您接收到一個在別處被填滿的緩衝區, 您可能需要在檢索內容之前將其翻 轉。例如,如果一個通道的 read()操作完成,而您想要查看被通道放入緩衝區內的數據,那 麼您需要在調用 get()之前翻轉緩衝區。通道對象在緩衝區上調用 put()增加數據;put 和 read 可以隨意混合使用。

布爾函數 hasRemaining()會在釋放緩衝區時告訴您是否已經達到緩衝區的上界。以下 是一種將數據元素從緩衝區釋放到一個數組的方法(在 2.1.10 小節中,我們將學到進行批量 傳輸的更高效的方法)。

for (int i = 0; buffer.hasRemaining(); i++) {
		myByteArray [i] = buffer.get();
}

作爲選擇,remaining()函數將告知您從當前位置到上界還剩餘的元素數目。您也可以 通過下面的循環來釋放圖 2-5 所示的緩衝區。

int count = buffer.remaining();
for (int i = 0; i < count, i++) {
		myByteArray [i] = buffer.get();
}

如果您對緩衝區有專門的控制,這種方法會更高效,因爲上界不會在每次循環重複時都被 檢查(這要求調用一個 buffer 樣例程序)。上文中的第一個例子允許多線程同時從緩衝區釋 放元素。

一旦緩衝區對象完成填充並釋放,它就可以被重新使用了。Clear()函數將緩衝區重置 爲空狀態。它並不改變緩衝區中的任何數據元素,而是僅僅將上界設爲容量的值,並把位置設 回 0,如圖 2.2 所示。這使得緩衝區可以被重新填入。參見示例 2.1。

例 2.1 填充和釋放緩衝區
 

/*
 * https://www.thomascook.com.cn/
 * © Copyright Thomas Cook 託邁酷客 2019 | 酷怡國際旅行社(上海)有限公司 All Rights Reserved.
 */

package javatest.niotest;

import java.nio.CharBuffer;

/**
 * Buffer fill/drain example.
 * This code uses the simplest means of filling and draining a buffer:
 * one element at * a time.
 *
 * @author Ron Hitchens ([email protected])
 */
public class BufferFillDrain {

    private static int index = 0;
    private static String[] strings = {
            "A random string value",
            "The product of an infinite number of monkeys",
            "Hey hey we're the Monkees",
            "Opening act for the Monkees: Jimi Hendrix",
            "'Scuse me while I kiss this fly", // Sorry Jimi ;-)
            "Help Me! Help Me!",
    };

    public static void main(String[] argv)
            throws Exception {
        CharBuffer buffer = CharBuffer.allocate(100);
        while (fillBuffer(buffer)) {
            buffer.flip();
            drainBuffer(buffer);
            buffer.clear();
        }
    }

    private static void drainBuffer(CharBuffer buffer) {
        while (buffer.hasRemaining()) {
            System.out.print(buffer.get());
        }
        System.out.println();
    }

    private static boolean fillBuffer(CharBuffer buffer) {
        if (index >= strings.length) {
            return false;
        }
        String string = strings[index++];
        for (int i = 0; i < string.length(); i++) {
            buffer.put(string.charAt(i));
        }
        return true;
    }
}

7)壓縮

package java.nio;

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

    // This is a partial API listing
    public abstract ByteBuffer compact();
}

  有時,您可能只想從緩衝區中釋放一部分數據,而不是全部,然後重新填充。爲了實現這 一點,未讀的數據元素需要下移以使第一個元素索引爲 0。儘管重複這樣做會效率低下,但這 有時非常必要,而 API 對此爲您提供了一個 compact()函數。這一緩衝區工具在複製數據時 要比您使用 get()和 put()函數高效得多。所以當您需要時,請使用 compact()。圖 2.6 顯示了一個我們已經釋放了一些元素,並且現在我們想要對其進行壓縮的緩衝區。

這樣操作:

buffer.compact();

會導致緩衝區的狀態如圖 2-7 所示:

這裏發生了幾件事。您會看到數據元素 2-5 被複制到 0-3 位置。位置 4 和 5 不受影響, 但現在正在或已經超出了當前位置,因此是“死的”。它們可以被之後的 put()調用重寫。 還要注意的是,位置已經被設爲被複制的數據元素的數目。也就是說,緩衝區現在被定位在緩 衝區中最後一個“存活”元素後插入數據的位置。最後,上界屬性被設置爲容量的值,因此緩 衝區可以被再次填滿。調用 compact()的作用是丟棄已經釋放的數據,保留未釋放的數據, 並使緩衝區對重新填充容量準備就緒。

您可以用這種類似於先入先出(FIFO)隊列的方式使用緩衝區。當然也存在更高效的算 法(緩衝區移位並不是一個處理隊列的非常高效的方法)。但是壓縮對於使緩衝區與您從端口 中讀入的數據(包)邏輯塊流的同步來說也許是一種便利的方法。

如果您想在壓縮後釋放數據,緩衝區會像之前所討論的那樣需要被翻轉。無論您之後是否 要向緩衝區中添加新的數據,這一點都是必要的。

8)標記

這本章節的開頭,我們已經涉及了緩衝區四種屬性中的三種。第四種,標記,使緩衝區能 夠記住一個位置並在之後將其返回。緩衝區的標記在 mark( )函數被調用之前是未定義的,調 用時標記被設爲當前位置的值。reset( )函數將位置設爲當前的標記值。如果標記值未定義,調 用 reset( )將導致 InvalidMarkException 異常。一些緩衝區函數會拋棄已經設定的標記 (rewind( ),clear( ),以及 flip( )總是拋棄標記)。如果新設定的值比當前的標記小,調用 limit( )或 position( )帶有索引參數的版本會拋棄標記。

讓我們看看這是如何進行的。在圖 2.5 的緩衝區上執行以下代碼將會導致圖 2-8 所顯示 的緩衝區狀態。

buffer.position(2).mark().position(4);

如果這個緩衝區現在被傳遞給一個通道,兩個字節(“ow”)將會被髮送,而位置會前 進到 6。如果我們此時調用 reset( ),位置將會被設爲標記,如圖 2-9 所示。再次將緩衝區傳 遞給通道將導致四個字節(“llow”)被髮送。

結果可能沒什麼意義(owllow 會被寫入通道),但您瞭解了概念。

9)比較

有時候比較兩個緩衝區所包含的數據是很有必要的。 所有的緩衝區都提供了一個常規的 equals( )函數用以測試兩個緩衝區的是否相等,以及一個 compareTo( )函數用以比較緩衝區。

package java.nio;

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

  	// This is a partial API listing
    public boolean equals(Object ob)

    public int compareTo(ByteBuffer that)
}

兩個緩衝區可用下面的代碼來測試是否相等:

if (buffer1.equals (buffer2)) { 
		doSomething( ); 
}

如果每個緩衝區中剩餘的內容相同,那麼 equals( )函數將返回 true,否則返回 false。 因爲這個測試是用於嚴格的相等而且是可換向的。前面的程序清單中的緩衝區名稱可以顛倒, 並會產生相同的結果。

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

    • 兩個對象類型相同。包含不同數據類型的 buffer 永遠不會相等,而且 buffer 絕不會等於非 buffer 對象。

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

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

如果不滿足以上任意條件,就會返回 false。

圖 2-10 說明了兩個屬性不同的緩衝區也可以相等。

圖 2-11 顯示了兩個相似的緩衝區,可能看起來是完全相同的緩衝區,但測試時會發現並 不相等。

緩衝區也支持用 compareTo( )函數以詞典順序進行比較。 這一函數在緩衝區參數小 於, 等於, 或者大於引用 compareTo( )的對象實例時, 分別返回一個負整數, 0 和正整 數。這些就是所有典型的緩衝區所實現的 java.lang.Comparable 接口語義。這意味着緩 衝區數組可以通過調用 java.util.Arrays.sort()函數按照它們的內容進行排序。

與 equals( )相似,compareTo( )不允許不同對象間進行比較。但 compareTo( )更爲嚴格:如 果您傳遞一個類型錯誤的對象,它會拋出 ClassCastException 異常,但 equals( )只會返回 false。

比較是針對每個緩衝區內剩餘數據進行的,與它們在 equals( )中的方式相同,直到不相等 的元素被發現或者到達緩衝區的上界。如果一個緩衝區在不相等元素髮現前已經被耗盡,較短 的緩衝區被認爲是小於較長的緩衝區。不像 equals( ),compareTo( )不可交換:順序問題。在本 例中,一個小於零的結果表明 buffer2 小於 buffer1,而表達式的值就會是 true:

if (buffer1.compareTo (buffer2) < 0) { 
		doSomething(); 
}

如果前面的代碼被應用到圖 2-10 所示的緩衝區中,結果會是 0,而 if 語句將毫無用 處。 被應用到圖 2-11 的緩衝區的相同測試將會返回一個正數(表明 buffer2 大於 buffer1),而這個表達式也會被判斷爲 false。

10)批量移動

緩衝區的涉及目的就是爲了能夠高效傳輸數據。一次移動一個數據元素,如例 2-1 所示 的那樣並不高效。如您在下面的程序清單中所看到的那樣,buffer API 提供了向緩衝區內 外批量移動數據元素的函數。


package java.nio;

public abstract class CharBuffer
        extends Buffer
        implements Comparable<CharBuffer>, Appendable, CharSequence, Readable {

    public abstract char get();

    public abstract char get(int index);

    public CharBuffer get(char[] dst)

    public CharBuffer get(char[] dst, int offset, int length)

    public abstract CharBuffer put(char c);

    public abstract CharBuffer put(int index, char c);

    public final CharBuffer put(char[] src)

    public CharBuffer put(char[] src, int offset, int length)

    public CharBuffer put(CharBuffer src)

    public final CharBuffer put(String src)

    public CharBuffer put(String src, int start, int end)
}

有兩種形式的 get( )可供從緩衝區到數組進行的數據複製使用。第一種形式只將一個數組 作爲參數,將一個緩衝區釋放到給定的數組。第二種形式使用 offset 和 length 參數來指 定目標數組的子區間。這些批量移動的合成效果與前文所討論的循環是相同的,但是這些方法 可能高效得多,因爲這種緩衝區實現能夠利用本地代碼或其他的優化來移動數據。

批量移動總是具有指定的長度。也就是說,您總是要求移動固定數量的數據元素。當參看 程序簽名時這一點還不明顯,但是對 get( )的這一引用:

buffer.get(myArray);

等價於:

buffer.get(myArray,0,myArray.length);

如果您所要求的數量的數據不能被傳送, 那麼不會有數據被傳遞, 緩衝區的狀態保持不 變,同時拋出 BufferUnderflowException 異常。因此當您傳入一個數組並且沒有指定長 度,您就相當於要求整個數組被填充。如果緩衝區中的數據不夠完全填滿數組,您會得到一個 異常。這意味着如果您想將一個小型緩衝區傳入一個大型數組,您需要明確地指定緩衝區中剩 餘的數據長度。上面的第一個例子不會如您第一眼所推出的結論那樣,將緩衝區內剩餘的數據 元素複製到數組的底部。要將一個緩衝區釋放到一個大數組中,要這樣做:

char [] bigArray = new char [1000]; 

// Get count of chars remaining in the buffer 
int length = buffer.remaining( ); 

// Buffer is known to contain < 1,000 chars 
buffer.get (bigArrray, 0, length); 

// Do something useful with the data 
processData (bigArray, length);

記住在調用 get( )之前必須查詢緩衝區中的元素數量(因爲我們需要告知 processData( )被 放置在 bigArray 中的字符個數)。調用 get( )會向前移動緩衝區的位置屬性,所以之後調用 remaining( )會返回 0。get( )的批量版本返回緩衝區的引用,而不是被傳送的數據元素的計數, 以減輕級聯調用的困難。

另一方面,如果緩衝區存有比數組能容納的數量更多的數據,您可以重複利用如下文所示 的程序塊進行讀取:

char [] smallArray = new char [10]; 

while (buffer.hasRemaining( )) {
		int length = Math.min (buffer.remaining(), smallArray.length);
		buffer.get (smallArray, 0, length);
		processData (smallArray, length); 
}

Put()的批量版本工作方式相似,但以相反的方向移動數據,從數組移動到緩衝區。他們 在傳送數據的大小方面有着相同的語義:

buffer.put(myArray);

等價於: 

buffer.put(myArray,0,myArray.length);

如 果 緩 衝 區 有 足 夠 的 空 間 接 受 數 組 中 的 數 據 (buffer.remaining()>myArray.length),數據將會被複制到從當前位置開始的緩衝 區,並且緩衝區位置會被提前所增加數據元素的數量。如果緩衝區中沒有足夠的空間,那麼不 會有數據被傳遞,同時拋出一個 BufferOverflowException 異常。

也可以通過調用帶有一個緩衝區引用作爲參數的 put()來在兩個緩衝區內進行批量傳 遞。

buffer.put(srcBuffer);

這等價於(假設 dstBuffer 有足夠的空間):

while (srcBuffer.hasRemaining()) { 
		dstBuffer.put (srcBuffer.get());
}

兩個緩衝區的位置都會前進所傳遞的數據元素的數量。範圍檢查會像對數組一樣進行。具 體來說,如果 srcBuffer.remaining( )大於 dstBuffer.remaining( ),那麼數據不 會被傳遞,同時拋出 BufferOverflowException 異常。如果您對將一個緩衝區傳遞給它 自己,就會引發 java.lang.IllegalArgumentException 異常。

在這一章節中我一直使用 CharBuffer 爲例,而且到目前爲止,這一討論也已經應用到 了其他的典型緩衝區上,比如 FloatBuffer,LongBuffer,等等。但是在下面的 API 程序 清單的最後兩個函數中包含了兩個只對 CharBuffer 適用的批量移動函數。

package java.nio;

public abstract class CharBuffer
        extends Buffer
        implements Comparable<CharBuffer>, Appendable, CharSequence, Readable {

    public final CharBuffer put(String src)

    public CharBuffer put(String src, int start, int end)
}

這些函數使用 String 作爲參數,而且與作用於 char 數組的批量移動函數相似。如所有 的 java 程序員所知,String 不同於 char 數組。但 String 確實包含 char 字符串,而且 我們人類確實傾向於將其在概念上認爲是 char 數組(尤其是我們中曾經是或者現在還是 C 或 C++ 程 序 員 的 那 些 人)。 由 於 這 些 原 因 , CharBuffer 類 提 供 了將 String 復 制 到 CharBuffer 中的便利方法。

String 移動與 char 數組移動相似,除了在序列上是由 start 和 end+1 下標確定(與 String.subString()類似),而不是 start 下標和 length。所以:

buffer.put(myString);

等價於:

buffer.put(myString,0,myString.length);

而這就是您怎樣複製字符 5-8,總共四個字符,從 myString 複製到 buffer。

buffer.put(myString,5,9);

String 批量移動等效於下面的代碼:

for (int i = start; i < end; i++) {
		buffer.put (myString.charAt (i)); 
}

對 String 要進行與 char 數組相同的範圍檢查。如果所有的字符都不適合緩衝區,將會 拋出 BufferOverflowException 異常。

2.創建緩衝區

就像我們在圖 2-1 所看到的那樣,有七種主要的緩衝區類,每一種都具有一種 Java 語 言中的非布爾類型的原始類型數據。(第 8 種也在圖中顯示出來,MappedByteBuffer,是 ByteBuffer 專門用於內存映射文件的一種特例。我們將會在第三章討論內存映射)。這些 類沒有一種能夠直接實例化。它們都是抽象類,但是都包含靜態工廠方法用來創建相應類的新 實例。

對於這一討論,我們將以 CharBuffer 類爲例,但是對於其它六種主要的緩衝區類也是 適用的:IntBuffer,DoubleBuffer, ShortBuffer, LongBuffer,FloatBuffer, 和 ByteBuffer。下面是創建一個緩衝區的關鍵函數,對所有的緩衝區類通用(要按照需要 替換類名):

package java.nio;

public abstract class CharBuffer
        extends Buffer
        implements Comparable<CharBuffer>, Appendable, CharSequence, Readable {

    public static CharBuffer allocate(int capacity)

    public static CharBuffer wrap(char[] array, int offset, int length)

    public static CharBuffer wrap(char[] array)

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

    public static CharBuffer wrap(CharSequence csq)

    public final boolean hasArray()

    public final char[] array()

    public final int arrayOffset()
}

新的緩衝區是由分配或包裝操作創建的。分配操作創建一個緩衝區對象並分配一個私有的 空間來儲存容量大小的數據元素。包裝操作創建一個緩衝區對象但是不分配任何空間來儲存數 據元素。它使用您所提供的數組作爲存儲空間來儲存緩衝區中的數據元素。

要分配一個容量爲 100 個 char 變量的 Charbuffer:

CharBuffer charBuffer = CharBuffer.allocate (100);

這段代碼隱含地從堆空間中分配了一個 char 型數組作爲備份存儲器來儲存 100 個 char 變量。

如果您想提供您自己的數組用做緩衝區的備份存儲器,請調用 wrap()函數:

char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);

這段代碼構造了一個新的緩衝區對象,但數據元素會存在於數組中。這意味着通過調用 put()函數造成的對緩衝區的改動會直接影響這個數組,而且對這個數組的任何改動也會對這 個緩衝區對象可見。帶有 offset 和 length 作爲參數的 wrap()函數版本則會構造一個按照 您提供的 offset 和 length 參數值初始化位置和上界的緩衝區。這樣做:

CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42);

創建了一個 position 值爲 12,limit 值爲 54,容量爲 myArray.length 的緩衝 區。

這個函數並不像您可能認爲的那樣,創建了一個只佔用了一個數組子集的緩衝區。這個緩 衝區可以存取這個數組的全部範圍;offset 和 length 參數只是設置了初始的狀態。調用使 用上面代碼中的方法創建的緩衝區中的 clear()函數, 然後對其進行填充, 直到超過上界 值,這將會重寫數組中的所有元素。Slice()函數(2.3 節將會討論)可以提供一個只佔用 備份數組一部分的緩衝區。

通過 allocate()或者 wrap()函數創建的緩衝區通常都是間接的(直接緩衝區會在 2.4.2 節討論)。間接的緩衝區使用備份數組,像我們之前討論的,您可以通過上面列出的 API 函數獲得對這些數組的存取權。Boolean 型函數 hasArray()告訴您這個緩衝區是否有 一個可存取的備份數組。如果這個函數的返回 true,array()函數會返回這個緩衝區對象所 使用的數組存儲空間的引用。

如果 hasArray()函數返回 false,不要調用 array()函數或者 arrayOffset()函 數。如果您這樣做了您會得到一個 UnsupportedOperationException 異常。如果一個緩 衝區是隻讀的,它的備份數組將會是超出上界的,即使一個數組對象被提供給 wrap()函數。 調用 array()函數或者 arrayOffset()會拋出一個 ReadOnlyBufferException 異常, 來阻止您得到存取權來修改只讀緩衝區的內容。如果您通過其它的方式獲得了對備份數組的存 取權限, 對這個數組的修改也會直接影響到這個只讀緩衝區。 只讀緩衝區將會在 2.3 節討 論。

最後一個函數, arrayOffset(), 返回緩衝區數據在數組中存儲的開始位置的偏移量 (從數組頭 0 開始計算)。如果您使用了帶有三個參數的版本的 wrap()函數來創建一個緩衝 區,對於這個緩衝區,arrayOffset()會一直返回 0,像我們之前討論的那樣。然而,如果 您切分了由一個數組提供存儲的緩衝區,得到的緩衝區可能會有一個非 0 的數組偏移量。這個 數組偏移量和緩衝區容量值會告訴您數組中哪些元素是被緩衝區使用的。緩衝區的切分會在 2.3 節討論。

到現在爲止, 這一節所進行的討論已經針對了所有的緩衝區類型。 我們用來做例子的 CharBuffer 提供了一對其它緩衝區類沒有的有用的便捷的函數:

package java.nio;

public abstract class CharBuffer
        extends Buffer
        implements Comparable<CharBuffer>, Appendable, CharSequence, Readable {

    public static CharBuffer wrap(CharSequence csq)

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

Wrap()函數創建一個只讀的備份存儲區是 CharSequence 接口或者其實現的的緩衝區 對象。 (CharSequence 對象將在第五章討論)。 Charsequence 描述了一個可讀的字符 流 。 像 JDK1.4 中 , 三 個 標 準 的 實 現 了 Charsequence 接 口 的 類 : String , StringBuffer,和 CharBuffer。 Wrap()函數可以很實用地“緩衝”一個已有的字符數 據, 通過緩衝區的 API 來存取其中的內容。 對於字符集解碼(第六章)和正則表達式處理 (第五章)這將是非常方便的。

CharBuffer charBuffer = CharBuffer.wrap ("Hello World");

三個參數的 wrap()函數版本使用 start 和 end 下標參數來描述傳入的 CharSequence 對象的子序列。 這是一個方便的類似於調用了 CharSequence.subsequence()函數的轉 換。Start 參數是序列中使用的第一個字符,end 是最後一個字符的下標值加 1。

3.複製緩衝區

如我們剛剛所討論的那樣,可以創建描述從外部存儲到數組中的數據元素的緩衝區對象。 但是緩衝區不限於管理數組中的外部數據。它們也能管理其他緩衝區中的外部數據。當一個管 理其他緩衝器所包含的數據元素的緩衝器被創建時,這個緩衝器被稱爲視圖緩衝器。大多數的 視圖緩衝器都是 ByteBuffer(參見 2.4.3 節)的視圖。在繼續前往字節緩衝器的細節之 前,我們先將注意力放在所有存儲器類型的共同視圖上。

視圖存儲器總是通過調用已存在的存儲器實例中的函數來創建。使用已存在的存儲器實例 中的工廠方法意味着視圖對象爲原始存儲器的內部實現細節私有。數據元素可以直接存取,無 論它們是存儲在數組中還是以一些其他的方式,而不需經過原始緩衝區對象的 get()/put() API。如果原始緩衝區是直接緩衝區,該緩衝區的視圖會具有同樣的效率優勢。映像緩衝區也 是如此(將於第三章討論)。

在這一章節中,我們將再次以 CharBuffer 爲例,但同樣的操作可被用於任何基本的緩 衝區類型(參見圖 2.1)。

package java.nio;

public abstract class CharBuffer
        extends Buffer
        implements Comparable<CharBuffer>, Appendable, CharSequence, Readable {

    // This is a partial API listing
    public abstract CharBuffer duplicate();

    public abstract CharBuffer asReadOnlyBuffer();

    public abstract CharBuffer slice();
}

Duplicate()函數創建了一個與原始緩衝區相似的新緩衝區。 兩個緩衝區共享數據元素,擁有同樣的容量,但每個緩衝區擁有各自的位置,上界和標記屬性。對一個緩衝區內的數 據元素所做的改變會反映在另外一個緩衝區上。這一副本緩衝區具有與原始緩衝區同樣的數據 視圖。如果原始的緩衝區爲只讀,或者爲直接緩衝區,新的緩衝區將繼承這些屬性。直接緩衝 區將在 2.4.2 節中討論。

緩衝區及其副本之間的聯繫如圖 2.12 所示。這是如下文所示的代碼產生的:

CharBuffer buffer = CharBuffer.allocate(8);
buffer.position(3).limit(6).mark().position(5);
CharBuffer dupeBuffer = buffer.duplicate();
buffer.clear();

您 可 以 使 用 asReadOnlyBuffer() 函 數 來 生 成 一 個 只 讀 的 緩 衝 區 視 圖 。 這 與 duplicate()相同,除了這個新的緩衝區不允許使用 put(),並且其 isReadOnly()函數 將 會 返 回 true 。 對 這 一 只 讀 緩 衝 區 的 put() 函 數 的 調 用 嘗 試 會 導 致 拋 出 ReadOnlyBufferException 異常。

分割緩衝區與複製相似, 但 slice()創建一個從原始緩衝區的當前位置開始的新緩衝 區,並且其容量是原始緩衝區的剩餘元素數量(limit-position)。這個新緩衝區與原始 緩衝區共享一段數據元素子序列。分割出來的緩衝區也會繼承只讀和直接屬性。圖 2-13 顯示 了以與下面代碼相似的代碼所生成的分割緩衝區:

CharBuffer buffer = CharBuffer.allocate(8);
buffer.position(3).limit(5);
CharBuffer sliceBuffer = buffer.slice();

要創建一個映射到數組位置 12-20(9 個元素)的 buffer 對象,應使用下面的代碼實現:

char[] myBuffer = new char[100];
CharBuffer cb = CharBuffer.wrap(myBuffer);
cb.position(12).limit(21);
CharBuffer sliced = cb.slice();

更詳細關於視圖 buffer 的討論參見 2.4.3 節。

4.字節緩衝區

在本章節中,我們將進一步觀察字節緩衝區。所有的基本數據類型都有相應的緩衝區類 (布爾型除外),但字節緩衝區有自己的獨特之處。字節是操作系統及其 I/O 設備使用的基 本數據類型。當在 JVM 和操作系統間傳遞數據時,將其他的數據類型拆分成構成它們的字節 是十分必要的。如我們在後面的章節中將要看到的那樣,系統層次的 I/O 面向字節的性質可 以在整個緩衝區的設計以及它們互相配合的服務中感受到。

爲了提供參考,以下是 ByteBuffer 的完整 API。這些函數有些已經在前面的章節中討 論,並且僅僅是針對具體類型的版本。新的函數將在本節以及後面的章節中涉及。

package java.nio;

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

    public static ByteBuffer allocate(int capacity)

    public static ByteBuffer allocateDirect(int capacity)

    public abstract boolean isDirect();

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

    public static ByteBuffer wrap(byte[] array)

    public abstract ByteBuffer duplicate();

    public abstract ByteBuffer asReadOnlyBuffer();

    public abstract ByteBuffer slice();

    public final boolean hasArray()

    public final byte[] array()

    public abstract byte get();

    public abstract ByteBuffer put(byte b);

    public abstract byte get(int index);

    public abstract ByteBuffer put(int index, byte b);

    public ByteBuffer get(byte[] dst, int offset, int length)

    public ByteBuffer get(byte[] dst)

    public ByteBuffer put(ByteBuffer src)

    public ByteBuffer put(byte[] src, int offset, int length)

    public final ByteBuffer put(byte[] src)

    public final int arrayOffset()

    public abstract char getChar();

    public abstract ByteBuffer putChar(char value);

    public abstract char getChar(int index);

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

    public abstract CharBuffer asCharBuffer();

    public abstract short getShort();

    public abstract ByteBuffer putShort(short value);

    public abstract short getShort(int index);

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

    public abstract ShortBuffer asShortBuffer();

    public abstract int getInt();

    public abstract ByteBuffer putInt(int value);

    public abstract int getInt(int index);

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

    public abstract IntBuffer asIntBuffer();

    public abstract long getLong();

    public abstract ByteBuffer putLong(long value);

    public abstract long getLong(int index);

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

    public abstract LongBuffer asLongBuffer();

    public abstract float getFloat();

    public abstract ByteBuffer putFloat(float value);

    public abstract float getFloat(int index);

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

    public abstract FloatBuffer asFloatBuffer();

    public abstract double getDouble();

    public abstract ByteBuffer putDouble(double value);

    public abstract double getDouble(int index);

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

    public abstract DoubleBuffer asDoubleBuffer();

    public abstract ByteBuffer compact();

    public boolean equals(Object ob)

    public int compareTo(ByteBuffer that)

    public String toString()

    public int hashCode()
}

1)字節順序

非字節類型的基本類型,除了布爾型 3 都是由組合在一起的幾個字節組成的。這些數據類 型及其大小總結在表 2-1 中。

每個基本數據類型都是以連續字節序列的形式存儲在內存中。 例如, 32 位的 int 值 0x037fb4c7(十進制的 58,700,999)可能會如圖 2-14 所顯示的那樣被塞入內存字節中 (內存地址從左往右增加)。注意前一個句子中的“可能”一詞。儘管字節大小已經被確定, 但字節順序問題一直沒有被廣泛認同。表示一個整型值的字節可能在內存中僅僅如圖 2-15 所 示的那樣被簡單地排列。

布爾型代表兩種值:true 或 false。一個字節可以包含 256 個唯一值,所以一個布爾值不能準確地對應到一個 或多個字節上。字節是所有緩衝區的構造塊。NIO 架構決定了布爾緩衝區的執行是有疑問的,而且對這種緩衝區類 型的需求不管怎麼說都是頗具爭議的。

多字節數值被存儲在內存中的方式一般被稱爲 endian-ness(字節順序)。如果數字數 值的最高字節——big end(大端),位於低位地址,那麼系統就是大端字節順序(如圖 214 所示)。如果最低字節最先保存在內存中,那麼小端字節順序(如圖 2-15 所示)。

字節順序很少由軟件設計者決定;它通常取決於硬件設計。字節順序的兩種類型有時被稱 爲字節性別,在當今被廣泛使用。兩種方式都具有自身的優勢。Intel 處理器使用小端字節 順序涉及。摩托羅拉的 CPU 系列、SUN 的 Sparc 工作站,以及 PowerPC 的 CPU 架構都採用 大端字節順序。

字節順序的問題甚至勝過CPU硬件設計。當Internet的設計者爲互聯各種類型的計算機 而設計網際協議(IP)時,他們意識到了在具有不同內部字節順序的系統間傳遞數值數據的 問題。因此,IP協議規定了使用大端的網絡字節順序概念 4。所有在IP分組報文的協議部分 中使用的多字節數值必須先在本地主機字節順序和通用的網絡字節順序之間進行轉換。

在 java.nio 中,字節順序由 ByteOrder 類封裝。

package java.nio;

public final class ByteOrder {

    public static final ByteOrder BIG_ENDIAN
    public static final ByteOrder LITTLE_ENDIAN

    public static ByteOrder nativeOrder()

    public String toString()
}

ByteOrder 類定義了決定從緩衝區中存儲或檢索多字節數值時使用哪一字節順序的常 量。這個類的作用就像一個類型安全的枚舉。它定義了以其本身實例預初始化的兩個 public 區域。只有這兩個 ByteOrder 實例總是存在於 JVM 中,因此它們可以通過使用--操作符進 行比較。 如果您需要知道 JVM 運行的硬件平臺的固有字節順序, 請調用靜態類函數 nativeOrder()。它將返回兩個已確定常量中的一個。調用 toString()將返回一個包含兩 個文字字符串 BIG_ENDIAN 或者 LITTLE_ENDIAN 之一的 String。

每個緩衝區類都具有一個能夠通過調用 order()查詢的當前字節順序設定。

package java.nio;

public abstract class CharBuffer extends Buffer implements Comparable, CharSequence {
// This is a partial API listing

    public final ByteOrder order()
}

這個函數從 ByteOrder 返回兩個常量之一。對於除了 ByteOrder 之外的其他緩衝區 類, 字節順序是一個只讀屬性, 並且可能根據緩衝區的建立方式而採用不同的值。 除了ByteBuffer , 其 他 通 過 分 配 或 包 裝 一 個 數 組 所 創 建 的 緩 衝 區 將 從 order() 返 回 與 ByteOrder.nativeOrder()相同的數值。這使因爲包含在緩衝區中的元素在 JVM 中將會被 作爲基本數據直接存取。

ByteBuffer 類有所不同:默認字節順序總是 ByteBuffer.BIG_ENDIAN,無論系統的 固有字節順序是什麼。Java 的默認字節順序是大端字節順序,這允許類文件等以及串行化的 對象可以在任何 JVM 中工作。如果固有硬件字節順序是小端,這會有性能隱患。在使用固有 硬件字節順序時,將 ByteBuffer 的內容當作其他數據類型存取(很快就會討論到)很可能 高效得多。

很可能您會對爲什麼 ByteBuffer 類需要一個字節順序設定這一問題感到困惑。字節就 是字節,對嗎?當然,但是如您不久將在 2.4.4 節所看到的那樣,ByteBuffer 對象像其他 基本數據類型一樣,具有大量便利的函數用於獲取和存放緩衝區內容。這些函數對字節進行編 碼或解碼的方式取決於 ByteBuffer 當前字節順序的設定。

ByteBuffer 的字符順序設定可以隨時通過調用以 ByteOrder.BIG_ENDIAN 或 ByteOrder.LITTL_ENDIAN 爲參數的 order()函數來改變。

import java.nio.ByteOrder;

public abstract class ByteBuffer extends Buffer implements Comparable { // This is a partial API listing

    public final ByteOrder order()

    public final ByteBuffer order(ByteOrder bo)
}

如果一個緩衝區被創建爲一個 ByteBuffer 對象的視圖(參見 2.4.3 節), 那麼 order()返回的數值就是視圖被創建時其創建源頭的 ByteBuffer 的字節順序設定。視圖的 字節順序設定在創建後不能被改變,而且如果原始的字節緩衝區的字節順序在之後被改變,它 也不會受到影響。。

2)直接緩衝區

字節緩衝區跟其他緩衝區類型最明顯的不同在於,它們可以成爲通道所執行的 I/O 的源 頭和/或目標。如果您向前跳到第三章(喂!喂!),您會發現通道只接收 ByteBuffer 作爲 參數。

如我們在第一章中所看到的那樣,操作系統的在內存區域中進行 I/O 操作。這些內存區 域,就操作系統方面而言,是相連的字節序列。於是,毫無疑問,只有字節緩衝區有資格參與 I/O 操作。也請回想一下操作系統會直接存取進程——在本例中是 JVM 進程的內存空間,以 傳輸數據。這也意味着 I/O 操作的目標內存區域必須是連續的字節序列。在 JVM 中,字節數 組可能不會在內存中連續存儲,或者無用存儲單元收集可能隨時對其進行移動。在 Java 中, 數組是對象,而數據存儲在對象中的方式在不同的 JVM 實現中都各有不同。

出於這一原因,引入了直接緩衝區的概念。直接緩衝區被用於與通道和固有 I/O 例程交 互。它們通過使用固有代碼來告知操作系統直接釋放或填充內存區域,對用於通道直接或原始 存取的內存區域中的字節元素的存儲盡了最大的努力。

直接字節緩衝區通常是 I/O 操作最好的選擇。在設計方面,它們支持 JVM 可用的最高效 I/O 機制。非直接字節緩衝區可以被傳遞給通道,但是這樣可能導致性能損耗。通常非直接緩 衝不可能成爲一個本地 I/O 操作的目標。如果您向一個通道中傳遞一個非直接 ByteBuffer 對象用於寫入,通道可能會在每次調用中隱含地進行下面的操作:

  1. 創建一個臨時的直接 ByteBuffer 對象。

  1. 將非直接緩衝區的內容複製到臨時緩衝中。

  1. 使用臨時緩衝區執行低層次 I/O 操作。

  1. 臨時緩衝區對象離開作用域,並最終成爲被回收的無用數據。

 

這可能導致緩衝區在每個 I/O 上覆制併產生大量對象,而這種事都是我們極力避免的。 不過,依靠工具,事情可以不這麼糟糕。運行時間可能會緩存並重新使用直接緩衝區或者執行 其他一些聰明的技巧來提高吞吐量。如果您僅僅爲一次使用而創建了一個緩衝區,區別並不是 很明顯。另一方面,如果您將在一段高性能腳本中重複使用緩衝區,分配直接緩衝區並重新使 用它們會使您遊刃有餘。

直接緩衝區時 I/O 的最佳選擇,但可能比創建非直接緩衝區要花費更高的成本。直接緩 衝區使用的內存是通過調用本地操作系統方面的代碼分配的,繞過了標準 JVM 堆棧。建立和 銷燬直接緩衝區會明顯比具有堆棧的緩衝區更加破費,這取決於主操作系統以及 JVM 實現。 直接緩衝區的內存區域不受無用存儲單元收集支配,因爲它們位於標準 JVM 堆棧之外。

使用直接緩衝區或非直接緩衝區的性能權衡會因JVM,操作系統,以及代碼設計而產生巨 大差異。通過分配堆棧外的內存,您可以使您的應用程序依賴於JVM未涉及的其它力量。當加 入其他的移動部分時,確定您正在達到想要的效果。我以一條舊的軟件行業格言建議您:先使 其工作,再加快其運行。不要一開始就過多擔心優化問題;首先要注重正確性。JVM實現可能 會執行緩衝區緩存或其他的優化,5 這會在不需要您參與許多不必要工作的情況下爲您提供所 需的性能。

直接 ByteBuffer 是通過調用具有所需容量的 ByteBuffer.allocateDirect()函數 產生的,就像我們之前所涉及的 allocate()函數一樣。注意用一個 wrap()函數所創建的被 包裝的緩衝區總是非直接的。

package java.nio;

public abstract class ByteBuffer extends Buffer implements Comparable { // This is a partial API listing 

    public static ByteBuffer allocate(int capacity)

    public static ByteBuffer allocateDirect(int capacity)

    public abstract boolean isDirect();
}

所有的緩衝區都提供了一個叫做 isDirect()的 boolean 函數,來測試特定緩衝區是否 爲直接緩衝區。雖然 ByteBuffer 是唯一可以被直接分配的類型,但如果基礎緩衝區是一個 直接 ByteBuffer, 對於非字節視圖緩衝區, isDirect()可以是 true。 這將我們帶到 了……

3)視圖緩衝區

就像我們已經討論的那樣,I/O 基本上可以歸結成組字節數據的四處傳遞。在進行大數據 量的 I/O 操作時,很又可能您會使用各種 ByteBuffer 類去讀取文件內容,接收來自網絡連 接的數據,等等。一旦數據到達了您的 ByteBuffer,您就需要查看它以決定怎麼做或者在 將它發送出去之前對它進行一些操作。ByteBuffer 類提供了豐富的 API 來創建視圖緩衝 區。

視圖緩衝區通過已存在的緩衝區對象實例的工廠方法來創建。這種視圖對象維護它自己的 屬性,容量,位置,上界和標記,但是和原來的緩衝區共享數據元素。我們已經在 2.3 節見 過了這樣的簡單例子,在例子中一個緩衝區被複制和切分。但是 ByteBuffer 類允許創建視 圖來將 byte 型緩衝區字節數據映射爲其它的原始數據類型。例如,asLongBuffer()函數 創建一個將八個字節型數據當成一個 long 型數據來存取的視圖緩衝區。

下面列出的每一個工廠方法都在原有的 ByteBuffer 對象上創建一個視圖緩衝區。調用 其中的任何一個方法都會創建對應的緩衝區類型,這個緩衝區是基礎緩衝區的一個切分,由基 礎緩衝區的位置和上界決定。新的緩衝區的容量是字節緩衝區中存在的元素數量除以視圖類型 中組成一個數據類型的字節數(參見表 2-1)。在切分中任一個超過上界的元素對於這個視圖 緩衝區都是不可見的。視圖緩衝區的第一個元素從創建它的 ByteBuffer 對象的位置開始 (positon()函數的返回值)。具有能被自然數整除的數據元素個數的視圖緩衝區是一種較 好的實現。

package java.nio;

public abstract class ByteBuffer extends Buffer implements Comparable {
// This is a partial API listing

    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 緩衝區的 CharBuffer 視圖,如圖 Figure 2-16 所示(Example 2-2 將這個框架用到了更大的範圍)

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

例 2-2. 創建一個 ByteBuffer 的字符視圖

package java.nio;

/**
 * Test asCharBuffer view.
 * <p>
 * * Created May 2002 * @author Ron Hitchens ([email protected])
 */
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) 'H');
        byteBuffer.put(2, (byte) 0);
        byteBuffer.put(3, (byte) 'i');
        byteBuffer.put(4, (byte) 0);
        byteBuffer.put(5, (byte) '!');
        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()
                + ": '" + buffer.toString() + "'");
    }
}
pos=0, limit=7, capacity=7: 'java.nio.HeapByteBuffer[pos=0 lim=7 cap=7]'

pos=0, limit=3, capacity=3: 'Hi!'

運行 BufferCharView 程序的輸出是:

pos=0, limit=7, capacity=7: 'java.nio.HeapByteBuffer[pos=0 lim=7 cap=7]'

pos=0, limit=3, capacity=3: 'Hi!

一 旦 您 得 到 了 視 圖 緩 衝 區 , 您 可 以 用 duplicate() , slice() 和 asReadOnlyBuffer()函數創建進一步的子視圖,就像 2.3 節所討論的那樣。

無論何時一個視圖緩衝區存取一個 ByteBuffer 的基礎字節,這些字節都會根據這個視 圖緩衝區的字節順序設定被包裝成一個數據元素。當一個視圖緩衝區被創建時,視圖創建的同 時它也繼承了基礎 ByteBuffer 對象的字節順序設定。這個視圖的字節排序不能再被修改。 在圖 2-16 中,您可以看到基礎 ByteBuffer 對象中的兩個字節映射成 CharBuffer 對象中 的一個字符。 字節順序設定決定了這些字節對是怎麼樣被組合成字符型變量的。 請參考 2.4.1 節獲取更多詳細的解釋。

當直接從 byte 型緩衝區中採集數據時,視圖換衝突擁有提高效率的潛能。如果這個視圖 的字節順序和本地機器硬件的字節順序一致,低等級的(相對於高級語言而言)語言的代碼可 以直接存取緩衝區中的數據值,而不是通過比特數據的包裝和解包裝過程來完成。

4)數據元素視圖

ByteBuffer 類提供了一個不太重要的機制來以多字節數據類型的形式存取 byte 數據 組。ByteBuffer 類爲每一種原始數據類型提供了存取的和轉化的方法:

 

package java.nio;

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 的字節數據,就好像一個數據元素被存儲在 那裏一樣。根據這個緩衝區的當前的有效的字節順序,這些字節數據會被排列或打亂成需要的 原始數據類型。比如說,如果 getInt()函數被調用,從當前的位置開始的四個字節會被包裝 成一個 int 類型的變量然後作爲函數的返回值返回。參見 2.4.1 節。

假設一個叫 buffer 的 ByteBuffer 對象處於圖 Figure 2-17 的狀態。

這段代碼:

int value = buffer.getInt();

會返回一個由緩衝區中位置 1-4 的 byte 數據值組成的 int 型變量的值。實際的返回值 取決於緩衝區的當前的比特排序(byte-order)設置。更具體的寫法是:

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

這將會返回值 0x3BC5315E,同時:

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

返回值 0x5E31C53B。

如 果您試圖 獲取的原 始類 型需要比 緩衝區中 存在 的字節數 更多的字 節, 會拋出 BufferUnderflowException。對於圖 Figure2-17 中的緩衝區,這段代碼會拋出異常, 因爲一個 long 型變量是 8 個字節的,但是緩衝區中只有 5 個字節:

long value = buffer.getLong();

這些函數返回的元素不需要被任何特定模塊界限所限制 6 。數值將會從以緩衝區的當前位 置開始的字節緩衝區中取出並組合,無論字組是否對齊。這樣的做法是低效的,但是它允許對 一個字節流中的數據進行隨機的放置。對於從二進制文件數據或者包裝數據成特定平臺的格式 或者導出到外部的系統,這將是非常有用的。

Put 函數提供與 get 相反的操作。原始數據的值會根據字節順序被分拆成一個個 byte 數據。如果存儲這些字節數據的空間不夠,會拋出 BufferOverflowException。

每一個函數都有重載的和無參數的形式。重載的函數對位置屬性加上特定的字節數。然後 無參數的形式則不改變位置屬性。

5)存取無符號數據

Java 編程語言對無符號數值並沒有提供直接的支持(除了 char 類型)。但是在許多情 況下您需要將無符號的信息轉化成數據流或者文件,或者包裝數據來創建文件頭或者其它帶有 無符號數據區域結構化的信息。ByteBuffer 類的 API 對此並沒有提供直接的支持,但是要 實現並不困難。您只需要小心精度的問題。當您必須處理緩衝區中的無符號數據時,例 2-3 中的工具類可能會非常有幫助。

例 2-3. 獲取/存放無符號值的工具程序

package javatest.niotest;

/**
 * 向 ByteBuffer 對象中獲取和存放無符號值的工具類。
 * 這裏所有的函數都是靜態的,並且帶有一個 ByteBuffer 參數。
 * 由於 java 不提供無符號原始類型,每個從緩衝區中讀出的無符號值被升到比它大的
 * 下一個基本數據類型中。
 * getUnsignedByte()返回一個 short 類型,
 * getUnsignedShort( ) 返回一個 int 類型,
 * 而 getUnsignedInt()返回一個 long 型。 There is no
 * 由於沒有基本類型來存儲返回的數據,因此沒有 getUnsignedLong( )。
 * 如果需要,返回 BigInteger 的函數可以執行。
 * 同樣,存放函數要取一個大於它們所分配的類型的值。
 * putUnsignedByte 取一個 short 型參數,等等。
 *
 * @author Ron Hitchens ([email protected])
 */
public class Unsigned {

    public static short getUnsignedByte(ByteBuffer bb) {
        return ((short) (bb.get() & 0xff));
    }

    public static void putUnsignedByte(ByteBuffer bb, int value) {
        bb.put((byte) (value & 0xff));
    }

    public static short getUnsignedByte(ByteBuffer bb, int position) {
        return ((short) (bb.get(position) & (short) 0xff));
    }

    public static void putUnsignedByte(ByteBuffer bb, int position,
                                       int value) {
        bb.put(position, (byte) (value & 0xff));
    }

    // ---------------------------------------------------------------
    public static int getUnsignedShort(ByteBuffer bb) {
        return (bb.getShort() & 0xffff);
    }

    public static void putUnsignedShort(ByteBuffer bb, int value) {
        bb.putShort((short) (value & 0xffff));
    }

    public static int getUnsignedShort(ByteBuffer bb, int position) {
        return (bb.getShort(position) & 0xffff);
    }

    public static void putUnsignedShort(ByteBuffer bb, int position,
                                        int value) {
        bb.putShort(position, (short) (value & 0xffff));
    }

    // ---------------------------------------------------------------
    public static long getUnsignedInt(ByteBuffer bb) {
        return ((long) bb.getInt() & 0xffffffffL);
    }

    public static void putUnsignedInt(ByteBuffer bb, long value) {
        bb.putInt((int) (value & 0xffffffffL));
    }

    public static long getUnsignedInt(ByteBuffer bb, int position) {
        return ((long) bb.getInt(position) & 0xffffffffL);
    }

    public static void putUnsignedInt(ByteBuffer bb, int position,
                                      long value) {
        bb.putInt(position, (int) (value & 0xffffffffL));
    }
}

6)內存映射緩衝區

映射緩衝區是帶有存儲在文件,通過內存映射來存取數據元素的字節緩衝區。映射緩衝區 通常是直接存取內存的,只能通過 FileChannel 類創建。映射緩衝區的用法和直接緩衝區類 似,但是 MappedByteBuffer 對象可以處理獨立於文件存取形式的的許多特定字符。出於這 個原因,我將討論映射緩衝區的內容留到 3.4 節,在那裏我們也會討論文件鎖。

 

5.總結

這一章介紹了 java.nio 包中的各種緩衝區。緩衝區對象使接下來幾章要介紹的高吞吐 率 I/O 得以實現。我們在這章討論了以下關鍵內容:

1)緩衝區屬性

所有緩衝區共有的屬性在 2.1.1 節涉及。這些屬性描述了緩衝區的當前狀態,影響了緩 衝區的表現。在這一節,我們也學習了怎樣改變緩衝區的狀態,以及如何增加及去除數 據元素。

2)創建緩衝區

我們在 2.2 節學會了如何創建緩衝區,以及在 2.3 節學習瞭如何複製它們。緩衝區有許 多類型。創建緩衝區的方式由緩衝區的使用方式和使用地點決定。

3)字節緩衝區

雖然緩衝區能夠創建來緩衝除了布爾類型的原始數據類型數據,字節緩衝區卻具有其他 緩衝區類型沒有的特徵。只有字節緩衝區能夠與通道共同使用(將於第三章討論),並且字節緩衝區提供了適用於其它數據類型的視圖。我們也解釋了字節排序的相關內容。 在 2.4 節我們討論了 ByteBuffer 類。

 

摘自JAVA NIO(中文版)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章