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 (ron@ronsoft.com)
 */
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 (ron@ronsoft.com)
 */
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 (ron@ronsoft.com)
 */
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(中文版)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值