NIO中的ByteBuffer是缓冲区,其中有几个比较重要的属性capacity,position和limit。
capacity:
其中,capacity是缓冲区的容量大小,在分配内存空间后不会改变。
limit:
limit是限制位置,在读写模式切换后limit的位置也跟着切换,切换到读限制位置和写限制位置。在写模式下limit一般位于capacity处,即不超出buffer的范围即可。在读模式下limit一般位于buffer中最后一个字符的位置,即在读的时候不超出存有内容的范围即可。
position:
position是写入位置或读取位置,在读写切换时也会随之切换。如写模式下随着字符的写入,position的位置会相应地后移,当切换为读模式时,position会一直最前方,此时其代表读取位置。读取操作结束后在切回写模式时,分为两种情况。第一种是使用clear进行切换,之前缓冲区里的全部元素,无论切换前是否读过,都直接抛弃,position移至buffer的最前方。另一种是compact,这种方式是保留没读过的元素,也就是读模式的position和读模式的limit之间的元素,切换写模式时会被保留,position会指向被保留元素的下一个。
如下列的一些图
一开始:
写入4字节:
flip切换为读模式,position切换到读取位置(最前方),limit切换为读取限制,有元素的最后一个位置:
读了4个字节后:
以clear切换为写模式,元素清空,limit和position也进行切换:
假设只读了两个字节,剩下的两个没读,并且是以compact方式进行的切换,那会保留没读过的元素,position和limit也切换为写模式,并且position位于保留元素的后方:
关于position还有需要注意的是, 我们在读模式下进行读取时,一般是调用buffer的get方法,这时每读一个字符指针就会后移一位。注意可以调用 rewind 方法将 position 重新置为 0,或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针。另外,使用mark 可以在读取时做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置,但是需注意rewind 和 flip 都会清除 mark 位置。
实战举例,解决网络收发数据时的半包粘包情况,灵活利用了ByteBuffer读写模式的切换以及对于position的灵活应用:
public static void main(String[] args) {
ByteBuffer source = ByteBuffer.allocate(32);
// 11 24
source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
split(source);
source.put("w are you?\nhaha!\n".getBytes());
split(source);
}
private static void split(ByteBuffer source) {
// 切换为读模式
source.flip();
// buffer中容量的总大小
int oldLimit = source.limit();
for (int i = 0; i < oldLimit; i++) {
// 使用source.get(i)的模式,读取位置position并不会后移
if (source.get(i) == '\n') {
System.out.println(i);
// 当前位置与position间的长度是新字符串长度,position是上一个串的结尾
ByteBuffer target = ByteBuffer.allocate(i + 1 - source.position());
// 0 ~ limit
source.limit(i + 1);
// 从source 读,向 target 写,position后移到该完整串的结尾
target.put(source);
debugAll(target);
source.limit(oldLimit);
}
}
// 有剩余的半包并未真正读取,切换写模式时保留,等后续内容到达补全
source.compact();
}