概念

  • Buffer接口是Java NIO的缓冲区的基础接口,定义了缓冲区操作的相关控制属性和操作方法。缓冲区是一个存放特定基础类型数据,如byte, char, int, long, float, double(不能是boolean),的容器,物理上是一个有界的线性数组。
  • Buffer接口的具体实现类,如byte, int, long, float, double等,包含对应的类型的数组,如IntBuffer则包含一个int[]数组,而操作,即读写所需的控制属性和方法,都统一在Buffer接口定义。
  • Buffer可以使用堆内存和直接内存,堆内存则使用类似于new int[capacity]来创建内部存储数据数组,每个元素都是具体类型的一个值;如果是使用直接内存,则通过Unsafe来进行内存操作,或者通过MMAP内存映射文件,如MappedByteBuffer。

控制属性

  • capacity:buffer的容量,即数组的大小,创建Buffer实例时指定之后不可改变。
  • limit:limit指向第一个不能进行读写的位置,limit不能大于capacity,主要与position配合算出数组哪些位置可以进行读写,即position和limit之间。
  • position:position指向下一个可以进行读或写的位置,position不能超过limit。初始化为0,即可以从头开始读写。
  • mark:标记某个位置,当之后调用reset时,可以回到这个位置,进行数据的重新读写,默认为-1。
  • 位置前后或大小关系:mark <= position <= limit <= capacity

核心方法

  • get/put:读/写数据,由每个具体的子类来实现具体的读写操作,可以是不指定索引的相对读写,即根据position位置读取下一个,如下:
public byte get() {
    return hb[ix(nextGetIndex())];
}
public ByteBuffer put(byte x) {
    hb[ix(nextPutIndex())] = x;
    return this;
}

也可以是指定索引的绝对读写:

public byte get(int i) {
    return hb[ix(checkIndex(i))];
}

public ByteBuffer get(byte[] dst, int offset, int length) {
    checkBounds(offset, length, dst.length);
    if (length > remaining())
        throw new BufferUnderflowException();
    System.arraycopy(hb, ix(position()), dst, offset, length);
    position(position() + length);
    return this;
}

public ByteBuffer put(int i, byte x) {
    hb[ix(checkIndex(i))] = x;
    return this;
}

public ByteBuffer put(byte[] src, int offset, int length) {
    checkBounds(offset, length, src.length);
    if (length > remaining())
        throw new BufferOverflowException();
    System.arraycopy(src, offset, hb, ix(position()), length);
    position(position() + length);
    return this;
}

除了调用buffer进行读写外,也可以通过Channel来对buffer进行读写,如channel.read(buffer)将channel的数据写到buffer,channel.write(buffer)从buffer读取数据并通过channel进行传输,此时使用的是相对读写。

  • mark/reset:标记/回溯,mark方法调用时,可以将mark属性设置为等于或小于当前position;reset方法则将当前position设置为等于mark的值,从而回溯重新读取数据,如果没有调用mark方法,而调用了reset,由于mark值默认为-1,则会抛异常。
/**
 * 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;
}
  • clear:清空buffer,以便对该buffer进行重新写,但是不是删除数据,而是将position重置为0,limit设置为等于capacity,mark重置为-1。
public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}

调用时机:当使用channel.read(buffer)从channel写数据到buffer,或者调用put方法写数据到buffer之前,调用clear先清空buffer:

buf.clear();     // Prepare buffer for reading
in.read(buf);    // Read data</pre></blockquote>
  • flip:写模式切换读模式。通常在对buffer写入一些数据之后,需要从buffer读出这些数据时,则因为写时,position在递增,故读之前,需要先调用flip方法来将limit设置为等于position,然后将position重置为0,从而position和limit之间的数据则是可以读取的。
public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

如调用channel.read(buffer)或者put写入数据到buffer,之后调用channel.write(buffer)或者get读取数据:

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
  • rewind:将position重置为0,一般用于重复读。
public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}

如下out.write从buf读取数据然后写出,然后调用rewind将position重置为0,故buf.get可以将一样的数据写到array中。

out.write(buf);    // Write remaining data
buf.rewind();      // Rewind buffer
buf.get(array);    // Copy data into array
  • compact:将未读取的数据拷贝到buffer的头部。
public ByteBuffer compact() {
    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
    position(remaining());
    limit(capacity());
    discardMark();
    return this;
}
public final int remaining() {
    return limit - position;
}

即先将position到limit之间的数据拷贝到数组索引0到limit-position的位置,然后将position设置为position的下一个位置,limit设置为等于capacity。compact通常用于在读数据还没完全从buffer读完,又要往buffer写数据的场景,如下:

buf.clear();          // Prepare buffer for use
while (in.read(buf) >= 0 || buf.position != 0) {
   buf.flip();
   out.write(buf);
   buf.compact();    // In case of partial write
}

从in读取数据并写到buf,调用buf.flip将position重置为0,limit为position;然后out从buf读取数据并写出。out可能没有读完buf中的数据,则调用buf.compact将未读完的数据拷贝到buf的头部位。然后in继续往buf写,之后out继续从position为0开始读,则可以继续读取刚刚没有读完的数据。但是要注意如果buf是只读的,则不能进行这个进行compact操作,会抛ReadOnlyBufferException异常。

线程安全

Buffer不是线程安全的,如果Buffer被多个线程共享,则需要使用线程同步机制,如sychronzied锁,来实现线程安全。

Buffer的类型

ByteBuffer、 CharBuffer、 DoubleBuffer、 FloatBuffer、 IntBuffer、 LongBuffer、ShortBuffer、MappedByteBuffer

推荐阅读:
Java NIO?看这一篇就够了!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐