ByteBuffer 学习笔记

143 阅读3分钟

前言

ByteBuffer 是 Java nio 包中 Buffer 的一个 子类抽象类

也是学习 Buffer 中的一个核心、重点

简介

在 nio 编程中,Channel 管道的数据是要写到 Buffer 中,才可以被程序操作的,由于 Channel 没有方向性,不像输入流输出流一样,有明确的方向性,Buffer 为了区分读写,引入了读模式、写模式进行区分

其实 Buffer 有多种

  1. ByteBuffer

  2. CharBuffer

  3. DoubleBuffer

  4. FloatBuffer

  5. IntBuffer

  6. LongBuffer

  7. ShortBuffer

  8. MappedByteBuffer..

但是 ByteBuffer 和 MappedByteBuffer 才是其中的核心,因为在网络传输中,我们实际中的就是字节形式进行传输

ByteBuffer 主要实现类

  • HeapByteBuffer
    • 占用的 jvm 中的堆内存
    • 读写操作 效率低 会收到GC影响
  • MappedByteBuffer(DirectByteBuffer)
    • 占用的操作系统的内存
    • 读写操作 效率高 不会收到GC影响
    • 不主动析构,会造成内存的泄露

获取 ByteBuffer 的方式

  • ByteBuffer.allocate(10);//一旦分配空间,不可以动态调整
  • encode()

ByteBuffer 的核心结构

ByteBuffer是一个类似数组的结构,整个结构中包含三个主要的状态

  • Capacity :buffer的容量,类似于数组的size
  • Position :buffer当前缓存的下标,在读取操作时记录读到了那个位置,在写操作时记录写到了那个位置。从0开始,每读取一次,下标+1
  • Limit :读写限制,在读操作时,设置了你能读多少字节的数据,在写操作时,设置了你还能写多少字节的数据

所谓的读写模式,本质上就是这几个状态的变化。主要有Position和Limit联合决定了Buffer的读写数据区域。

以下图片来自抖音 up 主孙哥

Buffer 中可以看到 这三个几个属性

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;

结合上面的图片食用,实际上 nio 开发中,针对 Buffer 说的所谓的读写模式,本质上就是这几个属性的值发生了变化,这几个值变成了,让你可以读、让你可以写的状态

核心 API

字节操作

将数据写入 buffer

  • channel 的 read 方法
// 将管道中的数据写入到缓冲区中
channel.read(buffer)
  • buffer的 put 方法
buffer.put(byte)    buffer.put((byte)'a')..
buffer.put(byte[])

从 buffer 中读数据

  • channel的write方法
// 将缓冲区的字节读取到管道
channel.write(buffer);
  • buffer的get方法
// 每次调用会影响 position 的位置
// 从 buffer 中获取一个字节
byte b = buffer.get();
  • buffer 的 rewind方法
// 可以将postion重置成0 ,用于复读数据。
buffer.rewind();

// 以下片段来自源码

    /**
     * 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;
    }
  • mark&reset方法,通过mark方法进行标记(position),通过reset方法跳回标记,从新执行.
buffer.mark();
// 源码
   public final Buffer mark() {
        mark = position;
        return this;
    }
buffer.reset();
// 源码
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }
  • get(index) 方法
// 获取特定position上的数据,但是不会对position的位置产生影响。
buffer.get(0);
// 源码
// HeapByteBuffer
public byte get(int i) {
        return hb[ix(checkIndex(i))];
}

// DirectByteBuffer
public byte get(int i) {
        return ((unsafe.getByte(ix(checkIndex(i)))));
}

字符串操作

字符串操作中,需要设置编码,如果不自定义编码的话,程序会调用系统默认编码,mac 是默认 utf-8,win 是默认 GBK。如果在 win 系统下,对字符串操作时,如果其中包含了汉字,那么就会有问题

将字符串写到 Buffer 中

  • nio 中的 Charset 工具类
ByteBuffer buffer = Charset.forName("UTF-8").encode("yzy");
  • nio 中的 StandardCharsets 类
ByteBuffer buffer = StandardCharsets.UTF_8.encode("yzy");
  • ByteBuffer.wrap 方法
ByteBuffer buffer = ByteBuffer.wrap("yzy".getBytes());
  • ByteBuffer 的 put 方法
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("yzy".getBytes());

Buffer 中的数据转换成字符串

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("杨".getBytes());

buffer.flip();
CharBuffer result = StandardCharsets.UTF_8.decode(buffer);
System.out.println("result.toString() = " + result.toString());