NIO网络编程(二)—— nio.ByteBuffer基础操作

NIO网络编程(二)—— 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的缓冲区时,状态会如下图:

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

在这里插入图片描述

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

在这里插入图片描述

创建缓冲区

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会加一,指向下一个可以放入的位置。

在这里插入图片描述

flip()

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

  • 如果是写模式 -> 读模式,position = 0 ,limit 指向最后一个元素的下一个位置,capacity不变
  • 如果是读 -> 写,则恢复为put()方法中的值

在这里插入图片描述

get()

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

在这里插入图片描述

rewind()

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

在这里插入图片描述

clean()

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

在这里插入图片描述

compact()

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

在这里插入图片描述

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可视化工具):
在这里插入图片描述

方法二

编码:通过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);//缓冲区可视化
    }
}

结果展示
在这里插入图片描述

方法三

编码:字符串调用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);
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值