在 Java 网络开发的过程中接触NIO是必不可少的,在NIO中有一个重要的组件那就是 ByteBuffer ,下面就来通过图文的方式来讲解ByteBuffer的使用以及一些操作的原理。
1. ByteBuffer实现原理
对于ByteBuffer来说主要有五个重要属性如下:
- mark(int类型): 记录当前索引的位置
- position(int类型): 读模式:表示接下来可以读取的数据位置, 写模式:表示可以写入数据的位置
- limit(int类型): 读模式:表示可以写入数据大小,写模式:表示可以写入数据大小。 默认是ByteBuffer的capacity
- capacity(int类型): ByteBuffer的容量
- hb(byte array): 实际数据存储byte数组
Tips: 几个数据之间的大小关系mark <= position <= limit <= capacity
示意图如下:
2. 读写模式
ByteBuffer主要有读写模式,Java原生的和 Netty 的ByteBuf有不同的。ByteBuffer的读写模式需要自己进行切换。
2.1 写模式
写模式示意图如下:
从上图可以看出来初始化后 capacity 是固定了。limit的值可以进行设置。当有新的数据写入position指针会进行移动。能写入的数据由limit确定。
2.2 读模式
读模式示意图如下:
如何把写入的数据读取出来,首先要将写模式转换成成读的模式。否则会读模式会在在写的指针往后进行读取。随着数据读取position指针也会进行移动,limit会限制指针移动的位置。
Tips: flip 方法用于读写模式切换
对于ByteBuffer主要是弄清楚四个变量 position、limit、mark、capacity 四者之间的关系转换以及读写的关系转换。
3. 使用示例
下面会结合例子以及示图来说明ByteBuffer的一些基本使用和一些常见API的操作。如下是一个简单的使用示例:
public class ByteBufferExample {
public static void main(String[] args) {
ByteBuffer allocate = ByteBuffer.allocate(); //分配一个大小为20bytes的ByteBuffer
System.out.println(allocate.capacity()); //
System.out.println(allocate.limit()); //
System.out.println(allocate.position()); //
System.out.println("--------------------");
allocate.putLong(L);
System.out.println(allocate.capacity());//
System.out.println(allocate.limit());//
System.out.println(allocate.position());//
System.out.println("--------------------");
System.out.println(allocate.getLong());
System.out.println(allocate.capacity());//
System.out.println(allocate.limit());//
System.out.println(allocate.position());//
}
}
不同的变量变化的示意图如下:
上面代码中没有进行读写模式转换的。position指针不管读还是写会一直往capacity位置靠近。
3.1 flip-API
使用示例代码如下:
public static void main(String[] args) throws exception {
ByteBuffer allocate = ByteBuffer.allocate(); //分配一个大小为20bytes的ByteBuffer
allocate.putLong(L);
System.out.println(allocate.capacity()); //
System.out.println(allocate.limit()); //
System.out.println(allocate.position()); //
System.out.println("--------------------");
allocate.flip();
System.out.println(allocate.capacity()); //
System.out.println(allocate.limit()); //
System.out.println(allocate.position()); //
System.out.println("--------------------");
System.out.println(allocate.getLong()); //
System.out.println(allocate.capacity()); //
System.out.println(allocate.limit()); //
System.out.println(allocate.position()); //
allocate.putLong(L); //throw exception
}
示意图如下:
从上面示意图可以看出调用方法 flip 的时候会将写入时候回的position指针的值赋给limit同时重置position的值到0的位置。这里就实现了读写的模式转换。如果再次读取的时候就能够将写入到ByteBuffer的值读取出来。
方法flip主要用于读写模式的切换
Tips: 如果你调用flip方法后读取的数据或者写入的数据超过了limit会有错误抛出
3.2 mark-API
使用代码如下:
public static void main(String[] args) throws Exception{
ByteBuffer allocate = ByteBuffer.allocate(); //分配一个大小为20bytes的 Byte Buffer
allocate.putLong(L);
allocate.putInt();
allocate.mark();
System.out.println(allocate.capacity());//
System.out.println(allocate.limit());//
System.out.println(allocate.position());//
System.out.println("-----------------------");
allocate.getLong();
System.out.println(allocate.capacity());//
System.out.println(allocate.limit());//
System.out.println(allocate.position());//
allocate.reset();
System.out.println("-----------------------");
System.out.println(allocate.capacity());//
System.out.println(allocate.limit());//
System.out.println(allocate.position());//
}
示意图如下:
从上图可以知道调用 mark 是将position的值赋给mark属性。然后你进行接下来的继续读写操作。当你需要将position恢复到标记字段的时候调用** reset ** 进行恢复。
Tips: 如果你调用mark然后又调用了flip,flip会将mark进行重置。
3.3 compact-API
使用代码实例如下:
public static void main(String[] args) throws Exception{
ByteBuffer allocate = ByteBuffer.allocate(); //分配一个大小为20bytes的ByteBuffer
allocate.putLong(L);
allocate.flip();
allocate.getInt();
System.out.println(allocate.capacity());//
System.out.println(allocate.limit());//
System.out.println(allocate.position());//
allocate.compact();
System.out.println("----------------------");
System.out.println(allocate.capacity());//
System.out.println(allocate.limit());//
System.out.println(allocate.position());//
}
示意图如下:
从上图可以看出来 compact 的主要作用: 用来清楚掉当前position指针之前的数据然后将指针指向limit的位置同时将整个指针往左移动直到替换掉position左边的数据,与此同时还会将limit的值设置为capacity。
4. 总结
ByteBuffer总体使用起来和Netty的ByteBuf对比没有Netty ByteBuf好用。但是对于使用原生的 Java NIO 的开发来说也是可以的。主要是需要用户自己对读写进行转换等操作,使用起来比较繁琐。但是整个ByteBuffer的实现还是比较简单的。