Java bytebuffer实践

748 阅读3分钟

Java bytebuffer实践

前言

最近在使用java做文件io相关的代码时,不可避免的使用的filechannel和bytebuffer,其中bytebuffer有些地方容易让初学者产生困扰,这里记录一下我的一些实践

类结构

基本原理

我们解释下Buffer类和ByteBuffer中的字段

  • hb:这个就是实际的byte数组
  • capacity:就是这个hb实际容量,绝大部分场景下都不可改变,无法增大
  • offset:内置的偏移量,每次读写buffer都必须加上offset,默认ByteBuffer.allocate的偏移量是0
  • position: 当前buffer 读写的位置,类似于cursor的概念,默认是0
  • limit: 当前buffer读写位置的上限,默认是capacity
  • mark:用来记录某个时刻position的位置,默认值是-1

// Invariants: mark <= position <= limit <= capacity

  1. 向buffer中写入内容: position变大
  2. 读取buffer中的内容:当写入完成时此时相当于0到position之间的内容都是刚刚写入的,那应该如何读取呢, 因为读写都是根据position来的,此时需要做一次flip操作,position需要被清零,limit换成之前的position
public final Buffer flip() {

    limit = position;

    position = 0;

    mark = -1;

    return this;

}

flip操作完之后会发现,limit和capacity之前有可能不一样了,下次在写入buffer的时候能写入的数据变小了

为什么要在写buffer完之后使用flip呢

因为写入到buffer中的只有0到position之间的数据,而position和就limit之间的数据是未定义的,不应该被应用程序读到,所以需要将最新的写入position设置为新的limit,这样应用程序读到limit就不读取数据了。

读完之后再写应该怎么处理

  • 假设完全读完了buffer,此时position和limit相等,对于这个buffer来说它的remaining是0了,不存在可写入的空间了,此时可以继续使用flip,当然更好的处理是使用rewind
public final Buffer rewind() {

    position = 0;

    mark = -1;

    return this;

}

具体的使用场景

  1. 初始化

// 初始化

ByteBuffer buf = ByteBuffer.allocate(4096);



public static ByteBuffer allocate(int capacity) {

    if (capacity < 0)

        throw new IllegalArgumentException();

    return new HeapByteBuffer(capacity, capacity);

}

可以看到实际初始化的是HeapByteBuffer实例,因为还有DirectBuffer,不过本文不会涉及

  1. 读写buffer

因为Filechannel配合在一起使用,而且经常和文件系统的读写在一个语境中使用,不免有些容易混淆的地方。

  • 从文件系统读取数据到buffer中,即使用fileChannel.read(buf)这个对于buffer来说其实是写入,当然该read会尽可能读满buffer
  • 通过filechannel和bytebuffer读取文件中第一个long number
fileChannel.read(buf);   // buf会被读满,即position==limit

buf.flip();              // position = 0; limit = old position

buf.getLong();           // position += 8;

所以我们看到从文件系统中读完之后要flip一下,在使用buf读写的时候要注意尽量读满再使用flip,否则应该使用rewind

  • 通过bytebuffer和filechannel写入一个long number到文件系统中
buf.putLong(1L);

buf.flip(); // 未写满的情况下,必须使用flip,写满的情况下flip和rewind是等价的

fileChannel.write(buf);

总结

  • 容易让人混淆的就是flip使用的时机,理解position和limit的关系之后就会简单不少