NIO学习 -Buffer

本文深入探讨Java NIO Buffer的工作原理,包括其基本属性、操作方法及如何在读写数据时使用。涵盖Buffer的mark、reset、clear、flip和rewind方法的详细解释。

Java NIO -Buffer

Buffer的定义

Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

Buffer的三个属性:capacity,position,limit

缓冲区是特定原始类型的元素的线性有限序列。除了其内容之外,缓冲区的基本属性还包括其capacity,limit和position:

缓冲区的capacity是它包含的元素数量。缓冲区的capacity永远不会为负,也不会改变。

capacity的理解:
一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

缓冲区的limit是不应读取或写入的第一个元素的索引。缓冲区的limit永远不会为负,也永远不会大于缓冲区的capacity。

limit的理解:
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

缓冲区的position是下一个要读取或写入的元素的索引。缓冲区的position永远不会为负,也不会大于其limit。postion的初始元素为0,最大值为capacity-1。

position的理解:
当读取数据时,也是从某个特定position读。当将Buffer从写模式切换到读模式,position会被reset为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的position。

mark和reset

缓冲区的markreset是调用该方法时将其positionreset到的索引。mark并非总是定义的,但是定义mark时,它永远不会为负,也永远不会大于position。如果定义了mark,则在将position或limit调整为小于mark的值时将其丢弃。如果未定义mark,则调用该reset方法 InvalidMarkException将引发。

对于mark,position,limit和capacity,以下不变式成立:

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

清除,翻转和倒带

除了访问position,limit和capacity值以及mark和reset的方法外,此类还定义了以下对缓冲区的操作:

clear()使缓冲区为新的通道读取或相对放置操作序列做好准备:将limit设置为capacity,并将position设置为零。

flip()使缓冲区为新的通道写入或相对get操作序列做好准备:将limit设置为当前position,然后将position设置为零。

rewind() 使缓冲区准备好重新读取它已经包含的数据:保留limit不变,并将position设置为零。

线程安全

缓冲区是线程安全的

使用Buffer读写数据一般遵循以下四个步骤:
1.写入数据到Buffer
2.调用flip()方法
3.从Buffer中读取数据
4.调用clear()方法或者compact()方法
写读->写读->写读

当向buffer写入数据时,buffer会记录下写了多少数据。
一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。
在读模式下,可以读取之前写入到buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
有两种方式能清空缓冲区:调用clear()或compact()方法。
clear()方法会清空整个缓冲区。
compact()方法只会清除已经读过的数据。
任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

例子:

        RandomAccessFile aFile = new RandomAccessFile("file/nio-data.txt", "rw");
        FileChannel inChannel = aFile.getChannel();

        //create buffer with capacity of 48 bytes
        ByteBuffer buf = ByteBuffer.allocate(48);
        //从此通道将字节序列读取到缓冲区中,相当于写入,如果 返回值= -1 标识通道已被读取到末尾。
        int bytesRead = inChannel.read(buf); //read into buffer.
        while (bytesRead != -1) {
            /**
             * 翻转此缓冲区。将limit设置为当前position,然后将该position设置为零。如果定义了mark,则将其丢弃。
             * 在执行一系列通道读取或放置操作之后,调用此方法以准备一系列通道写入或相对 get操作
             */
            buf.flip();  //make buffer ready for read
            //告诉当前position和极限之间是否有任何元素。 当且仅当此缓冲区中剩余至少一个元素时,才返回true
            while(buf.hasRemaining()){
                System.out.print((char) buf.get()); // read 1 byte at a time
            }
            //将缓冲区的position,limit设置初始化状态,不清空数据
            buf.clear(); //make buffer ready for writing
            //更新通道的状态。 inChannel.read(buf) = -1
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
        

向Buffer中写数据

写数据到Buffer有两种方式:

  1. 从Channel写到Buffer。
  2. 通过Buffer的put()方法写到Buffer里。

从Channel写到Buffer的例子:
int bytesRead = inChannel.read(buf); //read into buffer.
通过put方法写Buffer的例子:
buf.put(127);

put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。例如, 写到一个指定的位置,或者把一个字节数组写入到Buffer。 更多Buffer实现的细节参考JavaDoc。

flip()方法

flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。

从Buffer中读取数据

从Buffer中读取数据有两种方式:

  1. 从Buffer读取数据到Channel。
  2. 使用get()方法从Buffer中读取数据。
    从Buffer读取数据到Channel的例子:
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);

使用get()方法从Buffer中读取数据的例子
byte aByte = buf.get();
get方法有很多版本,允许你以不同的方式从Buffer中读取数据。例如,从指定position读取,或者从Buffer中读取数据到字节数组。更多Buffer实现的细节参考JavaDoc。

rewind()方法

Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。

clear()与compact()方法

一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

mark()与reset()方法

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。例如:

buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset();  //set position back to mark.

equals()与compareTo()方法

可以使用equals()和compareTo()方法两个Buffer。

equals()

当满足下列条件时,表示两个Buffer相等:

有相同的类型(byte、char、int等)。
Buffer中剩余的byte、char等的个数相等。
Buffer中所有剩余的byte、char等都相同。
如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。

compareTo()方法

compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:

第一个不相等的元素小于另一个Buffer中对应的元素 。
所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。
(译注:剩余元素是从 position到limit之间的元素)

源码分析:


    /**
     * 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
    //定义私有属性 标记,默认值为-1
    private int mark = -1;
    //定义私有属性 位置,默认值为0
    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;

    // 检查不变量后,使用给定mark,position,limit,capacity创建一个新缓冲区
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        //capacity容量必须大于0
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        //将设置limit的逻辑封装起来
        limit(lim);
        //同理
        position(pos);
        //如果设置了标记mark,则判断是否有mark大于position的情况,若有抛异常。
        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;
        //如果mark大于新位置,则mark被丢弃,设置-1默认值。
        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
     * ----------------------
     * 设置缓冲区的限制。如果position大于新limit,则将position设置为新limit指向的地方。如果是mark已定义且大于新limit,,则将其丢弃。
     */
    public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        limit = newLimit;
        if (position > newLimit) position = newLimit;
        if (mark > newLimit) 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
     * ----------------------
     * 将该缓冲区的位置reset为先前标记的位置。
     * 调用此方法不会改变和丢弃标记的值。     
     */
    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>
     * ----------------------
     * 清除此缓冲区。将position设为0,limit设置capacity的值和丢弃掉mark。
     * 
     * 在使用一系列channel.read() 或 buffer.put()操作之前,请调用此方法。
     * For example:
     * buf.clear(); //准备缓冲区以读取。
     * in.read(buf); //读取数据
     * 
     * 该方法实际上并不会擦除缓冲区中的数据。但他确实最常用于清除的情况,所以有了这个命名
     *
     * @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
     *----------------------
     * 翻转此缓冲区。limit设置为当前position的值,然后position设为0。如果定义了mark,则将其抛弃。
     * 在一系列通道channel.read或buffer.put之后,调用此方法为一系列channel.write或buffer.get操作做准备。
     * For example:
     * buf.put(magic) //设置缓冲区的header
     * in.read(buf); //将数据读入(写入)缓冲区
     * buf.flip(); //翻转缓冲区
     * out.write(buf); //将header+data写入通道
     *
     * 当将数据从一个地方传输到另一个地方时,该方法通常与compact方法结合使用。     
     */
    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>
     *
     * 倒带该缓冲区。position设为0且mark被丢弃。
     * 
     * 假设已经适当设置了限制,请在执行一系列channel.write 或 buffer.get()操作之前调用此方法。
     * For example:
     * out.write(buf); //将剩余的data写入buffer中
     * buf.rewind(); //倒带缓冲区,将position设为0,即可以重读buffer中的所有数据。
     * buf.get(array); //从buffer中读取数据到array中
     *
     *
     * @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.
     * 返回当前position和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.
     * 告诉当前position和limit之间是否有任何元素。
     * @return  <tt>true</tt> if, and only if, there is at least one element
     *          remaining in this buffer
     *          当且仅当次缓冲区中至少剩余一个元素就会返回true
     */
    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
     *
     * 告诉此缓冲区是否由可访问数组支持。
     * 如果此方法返回true,则可以安全地调用array 和arrayOffset方法。
     *
     */
    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
     * 
     * 返回支持此缓冲区的数组   (可选操作)。
     * 此方法旨在使支持数组的缓冲区更有效地传递给本机代码。具体的子类为此方法提供了更* 强类型的返回值。
     * 对该缓冲区内容的修改将导致返回数组的内容被修改,反之亦然。
     * 调用hasArray为了调用此方法,以确保这个缓冲器具有可访问的底层阵列之前方法。
     */
    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
     * 返回此缓冲区的第一个元素在此缓冲区的后备数组内的偏移量   (可选操作)。
     * 如果此缓冲区由数组支持,则缓冲区位置p 对应于数组索引p  +  arrayOffset()。
     * 
     * 调用hasArray为了调用此方法,以确保这个缓冲器具有可访问的底层阵列之前方法。
     */
    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
     * 告诉此缓冲区是否是直接缓冲区。
     * true 直接缓冲区
     */
    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
        int p = position;
        if (p >= limit)
            throw new BufferUnderflowException();
        position = p + 1;
        return p;
    }

    final int nextGetIndex(int nb) {                    // package-private
        int p = position;
        if (limit - p < nb)
            throw new BufferUnderflowException();
        position = p + 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
        int p = position;
        if (p >= limit)
            throw new BufferOverflowException();
        position = p + 1;
        return p;
    }

    final int nextPutIndex(int nb) {                    // package-private
        int p = position;
        if (limit - p < nb)
            throw new BufferOverflowException();
        position = p + 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();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值