阅读 83

Netty编程(二)—— nio.ByteBuffer基础操作

总述

流和通道之间的关键区别在于流是基于字节的,而通道是基于块的。流设计为按顺序一个字节接一个字节地传送数据。不同的是,通道会传送缓冲区中的数据块。可以读写通道的字节之前,这些字节必须已经存储在缓冲区中,而且一次会读/写一个缓冲区的数据。

可以把缓冲区看作是固定大小的元素列表,如数组,这些元素为某种特定类型,一般是基本数据类型。除了boolean外,Java的所有基本数据类型都有特定的Buffer子类:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer.LongBuffer、FloatBuffer和DoubleBuffer。每个子类中的方法都有相应类型的返回值和参数列表。例如,DoubleBuffer类有设置和获取double的方法。IntBuffer类有设置和获取int的方法。公共的Buffer超类只提供一些“通用”方法,这些方法不需要知道缓冲区所包含数据是什么类型(这里缺乏简单类型的泛型确实有不好的后果)。网络程序几乎只会使用ByteBuffer,但程序偶尔也会使用其他类型来取代ByteBuffer。

核心属性

字节缓冲区的父类Buffer中有几个核心属性,如下:

  • capacity:缓冲区的容量。通过构造函数赋予,一旦设置,无法更改

  • limit:缓冲区的界限。位于limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量

  • position下一个读写位置的索引(类似PC)。缓冲区的位置不能为负,并且不能大于limit

  • mark记录当前position的值。position被改变后,可以通过调用reset() 方法恢复到mark的位置。

以上四个属性必须满足以下要求  mark <= position <= limit <= capacity,例如可以通过下面的图来进一步了解这四个属性:

当新建一个capacity为10的缓冲区时,状态会如下图:

image.png

之后向缓冲区中添加三个元素a, b, c,由于这个时候还是读模式,所以此时position会变成3

image.png

现在我们切换到读模式(可以通过flip()方法,这个下面会介绍),此时,position会变成0(读的起始部位),limit变成3(读的限制)

image.png

创建缓冲区

allocate

基本的allocate()方法只返回一个有指定固定容量的新缓冲区,这是一个空缓冲区。例如,要创建一个容量为100的ByteBuffer:

ByteBuffer buffer1 = ByteBuffer.allocate(100); 复制代码

position位于缓冲区开始位置(也就是说,位置为0)。此外,用allocate()创建的缓冲区可以通过array()来访问。例如,可以使用下面的方法从缓冲区获取数据:

byte[] data1 = buffer1.array(); 复制代码

array()实际暴露了缓冲区的私有数据,同时修改得到的字节数组会反映到缓冲区中,反之亦然。所以需要小心不要修改字节数组而忘记缓冲区也会改变,反之亦然。

allocateDirect

ByteBuffer类还有一个allocateDirect()方法,这个方法不为缓冲区创建后备数组。从用法上看,allocateDirect()的使用与allocate()完全相同:

ByteBuffer buffer = ByteBuffer.allocateDirect(100); 复制代码

可以深入了解一下allocateDirect()与allocate()的区别,使用getClass()方法打印他们各自返回的对象类型,allocateDirect()返回的是java.nio.DirectByteBuffer,而allocate()返回的是java.nio.HeapByteBuffer。对于allocateDirect(),它使用的是直接内存,所以他的读写效率比较高,但是分配内存的效率比较低,而allocate()使用的是Java的堆内存,读写效率比较低,所以除非经过测量后发现性能确实是个问题,否则不应考虑使用直接缓冲区。

ByteBuffer常用方法

put()

put()方法可以将一个数据或者一个字节数组放入缓冲区,进行该操作后,缓冲区的position会加一,指向下一个可以放入的位置。

image.png

flip()

flip()方法会切换对缓冲区的操作模式,由写 -> 读 或者 读 -> 写,进行该操作后,会发生下面两种情况:

  • 如果是写模式 -> 读模式,position = 0 ,limit 指向最后一个元素的下一个位置,capacity不变

  • 如果是读 -> 写,则恢复为put()方法中的值

image.png

get()

get()方法会读取缓冲区中的一个值,进行该操作后,position会+1,如果超过了limit则会抛出异常,但是需要注意的是get(i)方法不会改变position的值。

image.png

rewind()

该方法只能在读模式下使用,使用rewind()方法后,会恢复position、limit和capacity的值,变为进行get()前的值

image.png

clean()

clean()方法会将缓冲区中的各个属性恢复为最初的状态,position = 0, capacity = limit,此时缓冲区的数据虽然依然存在,但是下次进行写操作时会覆盖这些数据。

image.png

compact()

此方法为ByteBuffer的方法,而不是Buffer的方法。compact会把未读完的数据向前压缩,然后切换到写模式,数据前移后,原位置的值并未清零,写时会覆盖之前的值。

image.png

mark()和reset()

mark()方法会将postion的值保存到mark属性中,mark做一个标记,记录position的位置,reset()方法会将position的值改为mark中保存的值 , reset是将position重置到mark的位置。

缓冲区和字符串的相互转换

方法一

编码:字符串调用getByte方法获得byte数组,将byte数组放入ByteBuffer中

解码:先调用ByteBuffer的flip方法(因为此时是写模式,读不出来数据),然后通过StandardCharsets的decoder方法解码,其中ByteBufferUtil.debugAll(buffer1);是可视化缓冲区的语句

public class Translate {     public static void main(String[] args) {         // 字符串 -> 缓冲区         String str1 = "hello";         ByteBuffer buffer1 = ByteBuffer.allocate(16);         // 通过字符串的getByte方法获得字节数组,放入缓冲区中         buffer1.put(str1.getBytes());         ByteBufferUtil.debugAll(buffer1);//缓冲区可视化         // 缓冲区 -> 字符串         buffer1.flip(); // 切换模式                  // 通过StandardCharsets解码,获得CharBuffer,再通过toString获得字符串         String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();         System.out.println(str2);         ByteBufferUtil.debugAll(buffer1);//缓冲区可视化     } } 复制代码

结果展示(使用了ByteBuffer可视化工具):

image.png

方法二

编码:通过StandardCharsets的encode方法获得ByteBuffer,此时获得的ByteBuffer为读模式,无需通过flip切换模式

解码:通过StandardCharsets的decoder方法解码

public class Translate {     public static void main(String[] args) {         // 字符串 -> 缓冲区         String str1 = "hello";         // 通过StandardCharsets的encode方法获得ByteBuffer,此时获得的ByteBuffer为读模式,无需通过flip切换模式         ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(str1);         ByteBufferUtil.debugAll(buffer1);//缓冲区可视化         // 缓冲区 -> 字符串         // 通过StandardCharsets解码,获得CharBuffer,再通过toString获得字符串         String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();         System.out.println(str2);         ByteBufferUtil.debugAll(buffer1);//缓冲区可视化     } } 复制代码

结果展示

image.png

方法三

编码:字符串调用getByte()方法获得字节数组,将字节数组传给ByteBuffer的wrap()方法,通过该方法获得ByteBuffer。同样无需调用flip方法切换为读模式。

解码:通过StandardCharsets的decoder方法解码

public class Translate {     public static void main(String[] args) {         // 字符串 -> 缓冲区         String str1 = "hello";                  // 通过StandardCharsets的encode方法获得ByteBuffer,此时获得的ByteBuffer为读模式,无需通过flip切换模式         ByteBuffer buffer1 = ByteBuffer.wrap(str1.getBytes());         ByteBufferUtil.debugAll(buffer1);         // 缓冲区 -> 字符串         // 通过StandardCharsets解码,获得CharBuffer,再通过toString获得字符串         String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();         System.out.println(str2);         ByteBufferUtil.debugAll(buffer1);     } } 复制代码

image.png


作者:JAVAWarrior
链接:https://juejin.cn/post/7028766298641268767


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐