Channel 和 ByteBuffer 的一些理解

280 阅读3分钟

ByteBuffer

HeapByteBuffer:堆内存,读写效率较低,受到 GC 的影响,在被 GC 之后,可能因为移动算法而移动
DirectByteBuffer:直接内存,读写效率高,使用的系统内存,不会受到 GC 影响,在内存中是固定的地址,少一次拷贝,但因需要调用操作系统函数,分配内存效率较低,而且使用不当可能内存泄漏

  • position : 缓冲区的位置,下一次读取或写入的位置
  • limit:还有多少数据需要写出(调用 read 方法后),或者还有多少空间可以读入数据(调用 write 后),初始和 capacity 一致,调用 flip() 后,limit 会变成 position,而 position 变成 0 
  • capacity:缓冲区的容量,即给缓冲区开了多大空间

常用方法

  • get()
    一次读取 buffer 的一个字节,position 向后 1 位,如果是有参构造方法则读取指定位置的数据,不移动 position
  • clear()
    position 置为 0 ,limit = capacity 相当于清空 buffer 了,***重新写,或者重新读***这个 byte[] image.png
  • put()
    往 buffer 写数据,从 position 位置开始覆盖写
// 打印的是 3456789,而不是 1 开始
    ByteBuffer allocate = ByteBuffer.allocate(10);
    allocate.put(new byte[]{1, 2});
//  allocate.flip();
    allocate.position(0);
    allocate.put(new byte[]{3, 4, 5, 6, 7, 8, 9});
    allocate.flip();
    while (allocate.hasRemaining()) {
        // 一次读1个字节,偏移量每次往后+1
        byte b = allocate.get();
        System.out.println(b);
    }
  • position
    指定 position
  • rewinnd
    也是将 position 设置为 0 ,mark = -1
  • flip()
    limit 会变成 position 限制住能读的位置,而 position 变成 0,为写或读做准备,读或写都是从 position 开始的,可以简单理解为切换成读模式,从数组下标第一个开始读 image.png
// 写
ByteBuffer allocate = ByteBuffer.allocate(10);
allocate.put(new byte[]{1,2,3,4,5,6,7,8,9,10});
// 如果不调用 flip 方法,会报BufferOverflowException
allocate.flip();
allocate.put(new byte[]{1,2,3,4,5,6,7,8,9,1});
byte b = allocate.get(9);
System.out.println(b);

// 读
ByteBuffer allocate = ByteBuffer.allocate(10);
allocate.put(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 1});
allocate.flip();
while (allocate.hasRemaining()) {
    // 一次读1个字节,偏移量每次往后+1
    byte b = allocate.get();
    System.out.println(b);
    // 每次读都调用该方法,会死循环,一直读第一位
    allocate.flip();
}
  • compact()
    可以理解为切换成写模式,把目前 position 之前的数据删掉,可以***从剩余数据接着往后写***。
    但其实是后面剩余的数据复制一份,长度为 limit - position 从 0 开始放,后面的数据不动,比如下面代码打印的是1234534500000(00000 是因为 limit 没限制住,byte[] 里默认是 0 ),而不是12345 image.png
        ByteBuffer allocate = ByteBuffer.allocate(10);
        allocate.put(new byte[]{1, 2, 3, 4, 5});
        // 切换成读模式
        allocate.flip();
        System.out.println(allocate.get());
        System.out.println(allocate.get());
        System.out.println(allocate.get());
        // 将已经读的覆盖掉,接着往后写
        allocate.compact();
        // position  = 0,从0 开始读,但如果调用的是 flip,则不会读两遍的90
        allocate.position(0);
        while (allocate.hasRemaining()) {
            // 一次读1个字节,偏移量每次往后+1
            byte b = allocate.get();
            System.out.println(b);
        }
  • mark()
    在当前 poisition 位置做标记
  • reset()
    回到 mark 的 position 位置

字符串和ByteBuffer互转

public static void main(String[] args) {
    // 字符串转为 ByteBuffer1,如果容量是5会报错
    ByteBuffer buffer = ByteBuffer.allocate(6);
    buffer.put("hello1".getBytes());

    // 字符串转为 ByteBuffer2,这样转不用担心ByteBuffer容量不够
    // 这样生成的 ByteBuffer 的 position 和 limit 都是 0 
    ByteBuffer hello2 = StandardCharsets.UTF_8.encode("hello2");

    // ByteBuffer 转 String 
    //1、需要 flip 方法
    buffer.flip();
    String s = StandardCharsets.UTF_8.decode(buffer).toString();
    System.out.println(s);

    // String 转 ByteBuffer2
    // hello2 这样直接可以读,不需要 flip,因为encode方法生成的pos是0
    String s1 = new String(hello2.array(), hello2.position(), hello2.limit(), StandardCharsets.UTF_8);
    System.out.println(s1);
}

FileChannel

  • channel.read(buffer)
    读取数据到剩余的缓冲区,其实就是读满当前 buffer position 以后的数据
// 文本是 1234567890abc,打印出来是 abc

    private static void read2() {
        // 获取 FileChannel
        try (FileChannel channel = new FileInputStream("src\file\data1.txt").getChannel()) {
            // 准备缓冲区,划出10字节做缓冲区,每次读入10字节的数据到buffer,下次read会从第11个字节开始读
            ByteBuffer buffer = ByteBuffer.allocate(10);

            // 读取到的字节数,为-1则读结束了
            int readLen = channel.read(buffer);
            int i = 0;
            while (readLen != -1) {
                // 打印 buffer 的内容
                // 切换到读模式
                buffer.flip();
                
                while (buffer.hasRemaining()) {
                    // 一次读1个字节,偏移量每次往后+1
                    byte b = buffer.get();
                    System.out.println((char) b);
                }
                System.out.println("-------------------------");
           
                i++;

                //切换为写模式,其实就是 position = 0,下次会从 11  个字符开始读
                buffer.clear();

                readLen = channel.read(buffer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }