Java NIO ByteBuffer原理使用图文详解

Java
233
0
0
2023-10-12

在 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的实现还是比较简单的。