Buffer

NIO的Buffer(缓冲区)本质上是一个内存块,既可以写入数据,也可以从中读取数据。NIO的Buffer类,是一个抽象类,位于java.nio包中,其内部是一个内存块(数组)。NIO的Buffer与普通的内存块(Java数组)不同的是:NIO Buffer对象,提供了一组更加有效的方法,用来进行写入和读取的交替访问。需要强调的是:Buffer类是一个非线程安全类。

Buffer类

Buffer类是一个抽象类,对应于Java的主要数据类型,在NIO中有8种缓冲区类,分别如下:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。前7种Buffer类型,覆盖了能在IO中传输的所有的Java基本数据类型。第8种类型MappedByteBuffer是专门用于内存映射的一种ByteBuffer类型。实际上,使用最多的还是ByteBuffer二进制字节缓冲区类型,后面会看到。

重要属性

Buffer类在其内部,有一个byte[]数组内存块,作为内存缓冲区。为了记录读写的状态和位置,Buffer类提供了一些重要的属性。其中,有三个重要的成员属性:capacity(容量)、position(读写位置)、limit(读写的限制)。除此之外,还有一个标记属性:mark(标记),可以将当前的position临时存入mark中;需要的时候,可以再从mark标记恢复到position位置。

capacity属性

Buffer类的capacity属性,表示内部容量的大小。一旦写入的对象数量超过了capacity容量,缓冲区就满了,不能再写入了。Buffer类的capacity属性一旦初始化,就不能再改变。原因是什么呢?Buffer类的对象在初始化时,会按照capacity分配内部的内存。在内存分配好之后,它的大小当然就不能改变了。再强调一下,capacity容量不是指内存块byte[]数组的字节的数量。capacity容量指的是写入的数据对象的数量。前面讲到,Buffer类是一个抽象类,Java不能直接用来新建对象。使用的时候,必须使用Buffer的某个子类,例如使用DoubleBuffer,则写入的数据是double类型,如果其capacity是100,那么我们最多可以写入100个double数据。

position属性

Buffer类的position属性,表示当前的位置。position属性与缓冲区的读写模式有关。在不同的模式下,position属性的值是不同的。当缓冲区进行读写的模式改变时,position会进行调整。在写入模式下,position的值变化规则如下:(1)在刚进入到写模式时,position值为0,表示当前的写入位置为从头开始。(2)每当
一个数据写到缓冲区之后,position会向后移动到下一个可写的位置。(3)初始的position值为0,最大可写值position为limit– 1。当position值达到limit时,缓冲区就已经无空间可写了。
在读模式下,position的值变化规则如下:(1)当缓冲区刚开始进入到读模式时,position会被重置为0。(2)当从缓冲区读取时,也是从position位置开始读。读取数据后,position向前移动到下一个可读的位置。(3)position最大的值为最大可读上限limit,当position达到limit时,表明缓冲区已经无数据可读。起点在哪里呢?当新建一个缓冲区时,缓冲区处于写入模式,这时是可以写数据的。数据写入后,如果要从缓冲区读取数据,这就要进行模式的切换,可以使用(即调用)flip翻转方法,将缓冲区变成读取模式。在这个flip翻转过程中,position会进行非常巨大的调整,具体的规则是:position由原来的写入位置,变成新的可读位置,也就是0,表示可以从头开始读。flip翻转的另外一半工作,就是要调整limit属性。

limit属性

Buffer类的limit属性,表示读写的最大上限。limit属性,也与缓冲区的读写模式有关。在不同的模式下,limit的值的含义是不同的。在写模式下,limit属性值的含义为可以写入的数据最大上限。在刚进入到写模式时,limit的值会被设置成缓冲区的capacity容量值,表示可以一直将缓冲区的容量写满。在读模式下,limit的值含义为最多能从缓冲区中读取到多少数据。一般来说,是先写入再读取。当缓冲区写入完成后,就可以开始从Buffer读取数据,可以使用flip翻转方法,这时,limit的值也会进行非常大的调整。具体如何调整呢?将写模式下的position值,设置成读模式下的limit值,也就是说,将之前写入的最大数量,作为可以读取的上限值

mark(标记)

比较简单。就是相当一个暂存属性,暂时保存position的值,方便后面的重复使用position值。

属性小结

这边使用一个简单的例子来说明:首先,创建缓冲区,容量为capacity,此时处于写模式,position为0,limit等于capacity,然后向缓冲区中写数据,每写入一个数据,position向后面移一个位置,就是加1,假定写入5个数据,position为5,这时,调用flip方法,将缓冲区从写模式切换为读模式,这时候limit会被设置为position的值,也就是5,表示,可以读5个数据,同时position会被设置为0,表示可以从0开始读。

属性说明
capacity容量,可以容纳的最大数据量,创建缓冲区时设置,且不能改变
limit上限,写模式表示可以写入的数据上限,读模式表示可以读取的数据上线
position位置,缓冲区中下一个要被读或写的元素的索引
markposition的暂存值

重要方法

allocate()创建缓冲区

IntBuffer intBuffer = IntBuffer.allocate(20);
System.out.println("posistion:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
输出:
posistion:0
limit:20
capacity:20

从上面的运行结果,可以看出:一个缓冲区在新建后,处于写入的模式,position写入位置为0,最大可写上限limit为的初始化值(这里是20),而缓冲区的容量capacity也是初始化值。

put()写入到缓冲区

在调用allocate方法分配内存、返回了实例对象后,缓冲区实例对象处于写模式,可以写入对象。要写入缓冲区,需要调用put方法。put方法很简单,只有一个参数,即为所需要写入的对象。不过,写入的数据类型要求与缓冲区的类型保持一致。

IntBuffer intBuffer = IntBuffer.allocate(20);
for (int i = 0; i < 5; i++) {
    intBuffer.put(i);
}

System.out.println("posistion:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
输出:
posistion:5
limit:20
capacity:20

从结果可以看到,position变成了5,指向了第6个可以写入的元素位置。而limit最大写入元素的上限、capacity最大容量的值,并没有发生变化。

flip()翻转

向缓冲区写入数据之后,是否可以直接从缓冲区中读取数据呢?呵呵,不能。这时缓冲区还处于写模式,如果需要读取数据,还需要将缓冲区转换成读模式。flip()翻转方法是Buffer类提供的一个模式转变的重要方法,它的作用就是将写入模式翻转成读取模式

IntBuffer intBuffer = IntBuffer.allocate(20);
for (int i = 0; i < 5; i++) {
    intBuffer.put(i);
}

System.out.println("posistion:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
System.out.println("写切成读");
intBuffer.flip();
System.out.println("posistion:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
输出:
posistion:5
limit:20
capacity:20
写切成读
posistion:0
limit:5
capacity:20

对flip()方法的从写入到读取转换的规则,详细的介绍如下:
首先,设置可读的长度上限limit。将写模式下的缓冲区中内容的最后写入位置position值,作为读模式下的limit上限值。
其次,把读的起始位置position的值设为0,表示从头开始读。
最后,清除之前的mark标记,因为mark保存的是写模式下的临时位置。在读模式下,如果继续使用旧的mark标记,会造成位置混乱。

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

新的问题来了,在读取完成后,如何再一次将缓冲区切换成写入模式呢?可以调用Buffer.clear()清空或者Buffer.compact()压缩方法,它们可以将缓冲区转换为写模式。
image.png

get()从缓冲区读取

调用flip方法,将缓冲区切换成读取模式。这时,可以开始从缓冲区中进行数据读取了。读数据很简单,调用get方法,每次从position的位置读取一个数据,并且进行相应的缓冲区属性的调整。

IntBuffer intBuffer = IntBuffer.allocate(20);
for (int i = 0; i < 5; i++) {
    intBuffer.put(i);
}
System.out.println("posistion:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
System.out.println("写切成读");
intBuffer.flip();
System.out.println("posistion:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());

System.out.println("读两个");
for (int i = 0; i <2 ; i++) {
    System.out.println(intBuffer.get());
}
intBuffer.flip();
System.out.println("posistion:"+intBuffer.position());
System.out.println("limit:"+intBuffer.limit());
System.out.println("capacity:"+intBuffer.capacity());
20
posistion:5
limit:20
capacity:20
写切成读
posistion:0
limit:5
capacity:20
读两个
0
1
posistion:0
limit:2
capacity:20

从程序的输出结果,我们可以看到,读取操作会改变可读位置position的值,而limit值不会改变。如果position值和limit的值相等,表示所有数据读取完成,position指向了一个没有数据的元素位置,已不能再读了。此时再读,会抛出BufferUnderflowException异常。这里强调一下,在读完之后,是否可以立即进行写入模式呢?不能。现在还处于读取模式,我们必须调用Buffer.clear()或Buffer.compact(),即清空或者压缩缓冲区,才能变成写入模式,让其重新可写。另外,还有一个问题:缓冲区是不是可以重复读呢?答案是可以的

rewind()倒带

已经读完的数据,如果需要再读一遍,可以调用rewind()方法。rewind()也叫倒带,就像播放磁带一样倒回去,再重新播放。
rewind()方法,主要是调整了缓冲区的position属性,具体的调整规则如下:
(1)position重置为0,所以可以重读缓冲区中的所有数据。
(2)limit保持不变,数据量还是一样的,仍然表示能从缓冲区中读取多少个元素。
(3)mark标记被清理,表示之前的临时位置不能再用了。
通过源代码,我们可以看到rewind()方法与flip()很相似,区别在于:rewind()不会影响limit属性值;而flip()会重设limit属性值。

mark( )和reset( )

Buffer.mark()方法的作用是将当前position的值保存起来,放在mark属性中,让mark属性记住这个临时位置;之后,可以调用Buffer.reset()方法将mark的值恢复到position中。也就是说,Buffer.mark()和Buffer.reset()方法是配套使用的。两种方法都需要内部mark属性的支持。在前面重复读取缓冲区的示例代码中,读到第3个元素(i==2时),调用mark()方法,把当前位置position的值保存到mark属性中,这时mark属性的值为2。接下来,就可以调用reset方法,将mark属性的值恢复到position中。然后可以从位置2(第三个元素)开始读。

clear( )清空缓冲区

在读取模式下,调用clear()方法将缓冲区切换为写入模式。此方法会将position清零,limit设置为capacity最大容量值,可以一直写入,直到缓冲区写满。在缓冲区处于读取模式时,调用clear(),缓冲区会被切换成写入模式。调用clear()之后,我们可以看到清空了position的值,即设置写入的起始位置为0,并且写入的上限为最大容量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值