NIO-Buffer使用

2018-06-25  本文已影响0人  alexwu59

ByteBuffer的概述

Buffer是java NIO的通道与I/O通信的入口,Buffer是通道向I/O发送数据的来源或者接受I/O数据的目的,一个Buffer对象是固定数量的一个容器,本质是一个基本类型的数组+属性信息。 

ByteBuffer是Buffer的一个子类,由于I/O通信底层都是字节通信,因此ByteBuffer是学习的重点。

ByteBuffer的使用

1.ByteBuffer创建

    ByteBuffer的创建是通过调用ByteBuffer的静态方法创建,不是通过new的方法,具体如下:

             ByteBuffer bb = ByteBuffer.allocate(1024);

2.ByteBuffer属性

 capacity: ByteBuffer的大小,创建ByteBuffer的时候指定,比如上面的1024,一旦创建好的ByteBuffer大小是不变的。

  limit:ByteBuffer中第一个不能被读或者被写的字节的位置,新创建的ByteBuffer中limit的值等于capacity

  position:ByteBuffer中下一个被读或者写的位置。ByteBuffer执行put/get方法的时候,会改变position的值,初始值为0。

  mark:一个备用标记,用于临时记录position,当ByteBuffer执行mark()的时候,mark的值就被设置为position的值。执行reset方法的时候,会把mark的值赋值给position,mark与reset是一对。mark在创建ByteBuffer的时候值为-1。

3.Bytebuffer API用法

创建一个Buffer 对象bf = ByteBuffer.allocate(8);

position=0,        limit = 8,         capacity=8,       mark=-1

执行bf.put((byte)’a’), bf.put((byte)’b’), 执行get 方法后,position 的值会加1

position=2,      limit = 8,       capacity=8,       mark=-1

注意:bf.put()方法有一个重载的方法:put(int index,byte b),该方法是把数据放到index 代

表的位置上,次方法操作后不会修改position 的值,同理get 方法跟put 一样。

执行bf.put(new byte[]{'c','d'}) 后:

position=5,          limit = 8,      capacity=8,     mark=-1

以上的方法都是用来向bf 填充数据,加入需要填充的数据只有这4 个字母,那么下一步就

是想bf 的内容发给socket 上。下面就需要执通道channel 的write 方法:

                                               channel.write(bf)

这个方法会把bf 中数据写入channel 的发送缓存区,channel 的write 的方法有个特点,他

只会发送position 与limit 之间的数据。如果此时调用write 方法,肯定会不能将abcd 发送

出去,因为此时bf 的position=4, limit = 8。因此需要将position 设置为a 的位置即0,将

limit 设置为d 的下一个位置即4,这样channel 才会把abcd 写出去。由于手动执行这些操

作太麻烦,NIO 提供了一个api 方法:flip(),这个方法可以完成上面的逻辑,flip 具体实现:

执行完flip()方法后,bf 如下图:

position=0,      limit = 4,       capacity=8,   mark=-1

再创建一个容量为8 的ByteBuffer bf:用来接收从通道传来的数据。

position=0,    limit = 8,      capacity=8,mark=-1

执行channel.read(bf),把通道缓存中的数据读入到bf 中,加入读入3 个字符:k ,n, m

position=3,     limit = 8,       capacity=8,    mark=-1

此时bf 中存储了k,n,m 三个字符,如果想获取k,n,m,需要使用bf.get(byte[] b)方法,把数

据一次性读入byte[]数组中,bf.get 的方法是从bf 的positon 位置开始一直读到limit 为止,

而此时position 为3,因此需要执行bf.flip()方法。修改position 和limit 的值:

position=0,       limit = 3,       capacity=8,    mark=-1

bf 有一个方法叫remaining(),该方法的作用就是获取limit 与position 之间的大小,也就是获取了本次要读取数据长度。因此在执行bf.get(byte[] b)之前需要创建一个数组,该数组的大小通常使用:

                  b = new byte[ bf. remaining()]

compact()方法:该方法的作用是把poistion 和limit 直接的数据移动到bf 的初始位置,具体实现:

public ByteBuffer compact() {

       //移动数组

      System.arraycopy(hb, ix(position()), hb, ix(0), remaining());

       //数组移动后把设置position 的值为下一个可写入bf 的位置

       position(remaining());

       //设置limit 的值为capacity

       limit(capacity());

       //销毁标记,mark=-1

       discardMark();

        return this;

}

假如bf 的状态入下:

position=0,     limit = 3,       capacity=8,   mark=-1

执行bf.get()操作后,变成:

position=1,       limit = 3,       capacity=8, mark=-1

在上图的基础上执行bf.compact():

position=2,      limit = 8,      capacity=8,   mark=-1

这里特别要注意的是索引2 里面的值仍然是m,因为compact 操作只是把1 和2 的值移动到了0 和1 的位置上,2 的位置上的值并没有清除,但是2 的位置已经成为下一个可以写入值得位置了。

mark()方法与reset()方法使用:

这两个方法,通过一个具体的例子来讲解:就是当一个ByteBuffer 的大小超过了channel 的缓存,那么channel 就不能一次性把byteBuffer 中的值写出去。sc 是channel,当执行完sc.write(bf),之后,bf 中的数据,还有剩余,比如bf 有8 个字节,sc.write()一次写出5,还剩余3 个,此时bf 的内存图如下:

position=5,      limit = 8,       capacity=8, mark=-1

因为没有写完,需要下次写事件发生时,需要接着从position=5 的地方输出后面的数据。bf 前5 个字节已经使用,那么bf 就有空闲的容量,可以接收新的数据存入bf,然后执行bf.compact()操作bf 的内存图如下:

position=3,       limit = 8,        capacity=8,      mark=-1

此时position=3 那么,bf 可以执行put()操作继续往bf 写入新数据,而不会担心覆盖老数据。当新的写事件到的时候,channel 又可以往出去写数据,需要把上次没有写完的数据写出去,如果直接执行write()方法,因为执行了compact()操作,position=3,而正确的数据是从position=0 开始,到position=3 为止,因为position 位置错误,导致write 的时候,老有数据写出,并且永远写不完。正确的做法是,在write 之前,把position 设置为0,limit 设置为3。limit=3 这个值如何设置呢?由于在上一次写操作后,执行完compact 后的position的值就是下一次写操作的limit 值。因此需要把position 备份一下,这里就用到了mark()操作,执行mark,把position 赋值给mark。如下图:

position=3,           limit = 8,              capacity=8,     mark=3

等到下一次写事件发生时,首先把mark 值取出来,赋值给limit,然后把position 设置为0。把mark 不能直接赋值给limit,首先要执行reset 操作,如下图:

position=3,     limit = 8,      capacity=8, mark=3

把mark 值赋值position,然后调用position()方法,把值赋给limit,

position=3,     limit = 3,      capacity=8,   mark=3

最后执行rewind 方法把position 赋0,把mark 设置为-1。

position=0,       limit = 3,             capacity=8, mark=-1

最后在执行write 方法,f,g,h 被正确写到通道中。

上一篇下一篇

猜你喜欢

热点阅读