skyitachi's blogJava ByteBuffer实践 2021-08-14
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
- 向buffer中写入内容: position变大
- 读取buffer中的内容:当写入完成时此时相当于0到position之间的内容都是刚刚写入的,那应该如何读取呢, 因为读写都是根据position来的,此时需要做一次flip操作,position需要被清零,limit换成之前的position
1 | public final Buffer flip() { |
flip操作完之后会发现,limit和capacity之前有可能不一样了,下次在写入buffer的时候能写入的数据变小了
为什么要在写buffer完之后使用flip呢
因为写入到buffer中的只有0到position之间的数据,而position和就limit之间的数据是未定义的,不应该被应用程序读到,所以需要将最新的写入position设置为新的limit,这样应用程序读到limit就不读取数据了。
读完之后再写应该怎么处理
- 假设完全读完了buffer,此时position和limit相等,对于这个buffer来说它的remaining是0了,不存在可写入的空间了,此时可以继续使用flip,当然更好的处理是使用rewind
1 | public final Buffer rewind() { |
具体的使用场景
1 | // 初始化 |
可以看到实际初始化的是HeapByteBuffer实例,因为还有DirectBuffer,不过本文不会涉及
因为Filechannel配合在一起使用,而且经常和文件系统的读写在一个语境中使用,不免有些容易混淆的地方。
- 从文件系统读取数据到buffer中,即使用
fileChannel.read(buf)
这个对于buffer来说其实是写入,当然该read会尽可能读满buffer - 通过filechannel和bytebuffer读取文件中第一个long number
1 | fileChannel.read(buf); // buf会被读满,即position==limit |
所以我们看到从文件系统中读完之后要flip一下,在使用buf读写的时候要注意尽量读满再使用flip,否则应该使用rewind
- 通过bytebuffer和filechannel写入一个long number到文件系统中
1 | buf.putLong(1L); |
总结
- 容易让人混淆的就是flip使用的时机,理解position和limit的关系之后就会简单不少