NIO之Buffer缓冲区

Buffer缓冲区

java.nio.Buffer是一个抽象类,该类有很多子类,这些子类可以存储不同的数据类型。关键的有ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer 。除了boolean,其他的基本数据类型都有提供,使用较多的是ByteBuffer和CharBuffer。由于这些类都是Buffer的子类,所以在使用起来比较类似,只是操作的数据类型不同。

每个缓冲区类都有一个静态方法 allocate(capacity)可以创建一个指定容量的缓冲区,都有一个 put()方法用于向缓冲区中存储数据, get()方法用于从缓冲区中读取数据。

下面介绍一些Buffer的概念

  • capacity :表示缓冲区可以存储多少个数据,由于Buffer本质就是一个数组,所以该容量创建后之后不能更改。 Buffer满了之后需要清空后才能继续写数据。
  • position:下一个要读取或写入的数据的位置。
  • limit:表示Buffer中可以操作数据的最高上限位置(limit 之后数据不能进行读写)。在 Buffer 的写模式下, limit 表示最多能够写入多少个数据; 在读取模式下, limit 表示最多可以读取多少个数据。
  • mark与reset:mark可以标记一个position,通过reset恢复到这个position。

上面这些概念的关系:0 <= mark <= position <= limit <= capacity

Buffer中的一些方法

capacity()方法返回缓冲区的大小
hasRemaining(),判断当前 position 后面是否还有可处理的数据, 即判断 postion 与 limit
之间是否还有数据可处理
limt() , 返回 limit 的位置
mark(),设置缓冲区的标志位置, 这个值只能在 0~position 之间。 以后可以通过 reset()返回到这个位置
position()可以返回 position 当前位置
remainig()返回当前 position 位置与 limit 之间的数据量
reset()方法可以将 position 设置为 mark 标志位
rewind(),将 position 设置为 0, 取消 mark 标志位
clear()不会清空缓冲区的数据,仅修改 position 为 0, 设置 limit 为 capacity, 缓冲区中数据还是存在的
flip()先把 limit 设置为 position 位置,再把position 设置为 0,可以从头读取数据

代码实例,使用Buffer读取和写入数据:

package com.monkey1024;


import java.nio.ByteBuffer;

/*
    Buffer读取和写入数据
 */
public class Buffer01 {
    public static void main(String[] args) {
        //创建一个长度是8的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(8);

        //向缓冲区中添加数据
        String str = "abc";
        buffer.put(str.getBytes());

        //将position移到0位置,这样可以从头读取数据
        buffer.flip();

        //从缓冲区中读取1个数据
        //System.out.println(buffer.get());
        //从缓冲区中读取多个数据
        byte[] bytes = new byte[buffer.limit()];
        buffer.get(bytes, 0, bytes.length);
        System.out.println(new String(bytes,0,bytes.length));
    }
}

使用mark标记位置:

package com.monkey1024;


import java.nio.ByteBuffer;

/*
    Buffer的mark和查看剩余数据大小
 */
public class Buffer02 {
    public static void main(String[] args) {
        //创建一个长度是8的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(8);

        //向缓冲区中添加数据
        String str = "abc";
        buffer.put(str.getBytes());

        buffer.flip();

        //从缓冲区中读取1个数据
        System.out.println(buffer.get());

        //标记当前位置
        buffer.mark();

        //从缓冲区中读取1个数据
        System.out.println(buffer.get());

        //将位置重置为之前标记的地方
        buffer.reset();
        System.out.println(buffer.get());

        //判断缓冲区中是否有剩余的数据
        if (buffer.hasRemaining()) {
            //查看剩余数据的大小
            System.out.println(buffer.remaining());
        }
    }
}

获取Buffer中的数组引用

package com.monkey1024;


import java.nio.ByteBuffer;
import java.util.Arrays;

/*
    获取Buffer中的数组引用
 */
public class Buffer03 {
    public static void main(String[] args) {
        //创建一个长度是8的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(8);

        //向缓冲区中添加数据
        String str = "abc";
        buffer.put(str.getBytes());

        buffer.flip();

        //从缓冲区中读取1个数据
        buffer.get();

        //将buffer的position设置为0,不会清空缓冲区的数据
        buffer.clear();
        //会覆盖当前position的数据
        buffer.put((byte) 65);

        byte[] array = buffer.array();
        System.out.println(Arrays.toString(array));

        //这里同时也会修改缓冲区的数据,说明该数组跟缓冲区的数组是同一个对象
        array[0] = 1;
        System.out.println(Arrays.toString(array));

    }
}

直接字节缓冲区

我们上面通过ByteBuffer创建的字节缓冲区是非直接字节缓冲区,除此之外还可以创建直接字节缓冲区,两者对比如下:

  • 非直接字节缓冲区使用的是堆内存,对应的类是HeapByteBuffer,适合处理小数据,原因是当GC之后数据的内存地址可能会发生变化,数据量较大的时候,移动数据会影响性能。
  • 直接字节缓冲区使用的是非堆内存,jvm进程内存,对应的类是DirectByteBuffer,执行性能高,但是相比于非直接字节缓冲区,在申请内存的时候会耗费更高的资源。适合操作大数据,IO操作频繁的数据。该部分的内存区域不受gc管理。

    通过下面代码创建直接字节缓冲区对象:

    ByteBuffer.allocateDirect(int capacity)