在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

示意图如下:

Java NIO ByteBuffer原理使用图文详解_数据

2\. 读写模式

ByteBuffer主要有读写模式,Java原生的和Netty的ByteBuf有不同的。ByteBuffer的读写模式需要自己进行切换。

2.1 写模式

写模式示意图如下:

Java NIO ByteBuffer原理使用图文详解_数据读取_02

从上图可以看出来初始化后capacity是固定了。limit的值可以进行设置。当有新的数据写入position指针会进行移动。能写入的数据由limit确定。

2.2 读模式

读模式示意图如下:

Java NIO ByteBuffer原理使用图文详解_数据读取_03

如何把写入的数据读取出来,首先要将写模式转换成成读的模式。否则会读模式会在在写的指针往后进行读取。随着数据读取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(20); //分配一个大小为20bytes的ByteBuffer
System.out.println(allocate.capacity()); //20
System.out.println(allocate.limit()); // 20
System.out.println(allocate.position()); //0
System.out.println("--------------------");
allocate.putLong(10L);
System.out.println(allocate.capacity());//20
System.out.println(allocate.limit());//20
System.out.println(allocate.position());//8
System.out.println("--------------------");
System.out.println(allocate.getLong());
System.out.println(allocate.capacity());//20
System.out.println(allocate.limit());//20
System.out.println(allocate.position());//16
}
}

不同的变量变化的示意图如下:

Java NIO ByteBuffer原理使用图文详解_java_04

上面代码中没有进行读写模式转换的。position指针不管读还是写会一直往capacity位置靠近。

3.1 flip-API

使用示例代码如下:

public static void main(String[] args) throws Exception{

ByteBuffer allocate = ByteBuffer.allocate(20); //分配一个大小为20bytes的ByteBuffer
allocate.putLong(10L);
System.out.println(allocate.capacity()); //20
System.out.println(allocate.limit()); // 20
System.out.println(allocate.position()); //8
System.out.println("--------------------");
allocate.flip();
System.out.println(allocate.capacity()); //20
System.out.println(allocate.limit()); // 8
System.out.println(allocate.position()); //0
System.out.println("--------------------");
System.out.println(allocate.getLong()); //10
System.out.println(allocate.capacity()); //20
System.out.println(allocate.limit()); // 8
System.out.println(allocate.position()); //8
allocate.putLong(10L); //throw exception

}

示意图如下:

Java NIO ByteBuffer原理使用图文详解_java_05

从上面示意图可以看出调用方法 **​​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(20); //分配一个大小为20bytes的ByteBuffer
allocate.putLong(10L);
allocate.putInt(1);
allocate.mark();
System.out.println(allocate.capacity());//20
System.out.println(allocate.limit());//20
System.out.println(allocate.position());//12
System.out.println("-----------------------");
allocate.getLong();
System.out.println(allocate.capacity());//20
System.out.println(allocate.limit());//20
System.out.println(allocate.position());//20
allocate.reset();
System.out.println("-----------------------");
System.out.println(allocate.capacity());//20
System.out.println(allocate.limit());//20
System.out.println(allocate.position());//12
}

示意图如下:

Java NIO ByteBuffer原理使用图文详解_java_06

从上图可以知道调用 **​​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(20); //分配一个大小为20bytes的ByteBuffer
allocate.putLong(10L);
allocate.flip();
allocate.getInt();
System.out.println(allocate.capacity());//20
System.out.println(allocate.limit());//8
System.out.println(allocate.position());//4
allocate.compact();
System.out.println("----------------------");
System.out.println(allocate.capacity());//20
System.out.println(allocate.limit());//20
System.out.println(allocate.position());//4
}

示意图如下:

Java NIO ByteBuffer原理使用图文详解_java_07

从上图可以看出来 **​​compact​​ **的主要作用: 用来清楚掉当前position指针之前的数据然后将指针指向limit的位置同时将整个指针往左移动直到替换掉position左边的数据,与此同时还会将limit的值设置为capacity。

4\. 总结

ByteBuffer总体使用起来和Netty的ByteBuf对比没有Netty ByteBuf好用。但是对于使用原生的Java NIO的开发来说也是可以的。主要是需要用户自己对读写进行转换等操作,使用起来比较繁琐。但是整个ByteBuffer的实现还是比较简单的。