数据结构论坛

首页 » 分类 » 分类 » 清华教授手抄NIOBuffer类与属性
TUhjnbcbe - 2025/1/12 21:03:00
在北京治疗白癜风那家医院比较好 https://wapjbk.39.net/yiyuanzaixian/bjzkbdfyy/nxbdf/
北京治疗白癜风哪里好 https://disease.39.net/yldt/bjzkbdfyy/

分享的是《Redis五种数据结构以及三种高级数据结构解析》,这篇给大家分享《详解NIOBuffer类》。

详解NIOBuffer类

NIO的Buffer本质上是一个内存块,既可以写入数据,也可以从中读取数据。JavaNIO中代表缓冲区的Buffer类是一个抽象类,位于java.nio包中。

NIO的Buffer内部是一个内存块(数组),与普通的内存块(Java数组)不同的是:NIOBuffer对象提供了一组比较有效的方法,用来进行写入和读取的交替访问。

tips:Buffer类是一个非线程安全类。(Buffersarenotsafeforusebymultipleconcurrentthreads.)

1Buffer类API介绍

Buffer类是一个抽象类,对应于Java的主要数据类型。在NIO中,有8种缓冲区类,ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer。前7种Buffer类型覆盖了能在IO中传输的所有Java基本数据类型,第8种类型是一种专门用于内存映射的ByteBuffer类型。

使用最多的是ByteBuffer(二进制字节缓冲区)类型

1.1Buffer类的重要属性

Buffer的子类会拥有一块内存,作为数据的读写缓冲区,但是读写缓冲区并没有定义在Buffer基类中,而是定义在具体的子类中。例如,ByteBuffer子类就拥有一个byte[]类型的数组成员finalbyte[]hb,可以作为自己的读写缓冲区,数组的元素类型与Buffer子类的操作类型相对应。

publicabstractclassByteBufferextendsBufferimplementsComparableByteBuffer{

finalbyte[]hb;

}

为了记录读写的状态和位置,Buffer类额外提供了一些重要的属性,其中有三个重要的成员属性:capacity(容量)、position(读写位置)和limit(读写的限制)

1.1.1capacity属性,position属性,limit属性

capacity属性Buffer类的capacity属性表示内部容量的大小。一旦写入的对象数量超过了capacity,缓冲区就满了,不能再写入了。

Buffer类的capacity属性一旦初始化,就不能再改变。

Buffer类的对象在初始化时会按照capacity分配内部数组的内存,在数组内存分配好之后,它的大小就不能改变了。

position属性Buffer类的position属性表示当前的位置。

position属性的值与缓冲区的读写模式有关。在不同的模式下,position属性值的含义是不同的,在缓冲区进行读写的模式改变时,position值会进行相应的调整。

在写模式下,position值的变化规则如下:

(1)在刚进入写模式时,position值为0,表示当前的写入位置为从头开始。

(2)每当一个数据写到缓冲区之后,position会向后移动到下一个可写的位置。

(3)初始的position值为0,最大可写值为limit-1。当position值达到limit时,缓冲区就已经无空间可写了。

在读模式下,position值的变化规则如下:

(1)当缓冲区刚开始进入读模式时,position会被重置为0。

(2)当从缓冲区读取时,也是从position位置开始读。读取数据后,position向前移动到下一个可读的位置。

(3)在读模式下,limit表示可读数据的上限。position的最大值为最大可读上限limit,当position达到limit时表明缓冲区已经无数据可读。

当新建了一个缓冲区实例时,缓冲区处于写模式,这时是可以写数据的。在数据写入完成后,如果要从缓冲区读取数据,就要进行模式的切换,可以调用flip()方法将缓冲区变成读模式,flip为翻转的意思。

在从写模式到读模式的翻转过程中,position和limit属性值会进行调整,具体的规则是:

(1)limit属性被设置成写模式时的position值,表示可以读取的最大数据位置。

(2)position由原来的写入位置变成新的可读位置,也就是0,表示可以从头开始读。

也就是说flip把limit设为position,再把position设为0。

limit属性Buffer类的limit属性表示可以写入或者读取的数据最大上限,其属性值的具体含义也与缓冲区的读写模式有关。

在写模式下,limit属性值的含义为可以写入的数据最大上限。在刚进入写模式时,limit的值会被设置成缓冲区的capacity值,表示可以一直将缓冲区的容量写满。

在读模式下,limit值的含义为最多能从缓冲区读取多少数据。

一般来说,在进行缓冲区操作时是先写入再读取的。当缓冲区写入完成后,就可以开始从Buffer读取数据,调用flip()方法(翻转),这时limit的值也会进行调整。将写模式下的position值设置成读模式下的limit值,也就是说,将之前写入的最大数量作为可以读取数据的上限值。

Buffer在翻转时的属性值调整主要涉及position、limit两个属性,下面举一个简单的例子:

(1)首先,创建缓冲区。新创建的缓冲区处于写模式,其position值为0,limit值为最大容量capacity。

(2)然后,向缓冲区写数据。每写入一个数据,position向后面移动一个位置,也就是position的值加1。这里假定写入了5个数,当写入完成后,position的值为5。

(3)最后,使用flip方法将缓冲区切换到读模式。limit的值会先被设置成写模式时的position值,所以新的limit值是5,表示可以读取数据的最大上限是5。之后调整position值,新的position会被重置为0,表示可以从0开始读。

标记属性:mark(标记)属性

在缓冲区操作过程当中,可以将当前的position值临时存入mark属性中;需要的时候,再从mark中取出暂存的标记值,恢复到position属性中,重新从position位置开始处理。

这里主要涉及两个方法:

mark方法:将当前的position值临时存入mark属性中reset方法:恢复到position属性中1.2Buffer类中的重要方法

详细介绍Buffer类的几个常用方法,包含Buffer实例的创建、写入、读取、重复读、标记和重置等。

1.2.1allocate():创建Buffer实例

Test

publicvoidtestAllocate(){

//参数:指定capacity属性,不能小于0

ByteBufferbyteBuffer=ByteBuffer.allocate();

System.out.println(position=+byteBuffer.position());

System.out.println(capacity=+byteBuffer.capacity());

System.out.println(limit=+byteBuffer.limit());

}

输出结果:

position=0

capacity=

limit=

一个缓冲区在新建后处于写模式,position属性(代表写入位置)的值为0,缓冲区的capacity值是初始化时allocate方法的参数值(这里是),而limit最大可写上限值也为allocate方法的初始化参数值。

1.2.2put方法:写入数据

在调用allocate()方法分配内存、返回了实例对象后,缓冲区实例对象处于写模式,可以写入对象,

如果要把对象写入缓冲区,就需要调用put()方法。put()方法很简单,只有一个参数,即需要写入的对象,只不过要求写入的数据类型与缓冲区的类型保持一致。

Test(expected=BufferOverflowException.class)

publicvoidtestPut(){

IntBufferbuffer=IntBuffer.allocate(5);

buffer.put(1);

buffer.put(2);

buffer.put(3);

buffer.put(4);

buffer.put(5);

System.out.println(position=+buffer.position());

System.out.println(capacity=+buffer.capacity());

System.out.println(limit=+buffer.limit());

//转成数数组

int[]array=buffer.array();

Arrays.stream(array).forEach(System.out::print);

//前面已经写入5个数据了,尝试在写入就会抛出异常:BufferOverflowException

buffer.put(6);

}

输出:

position=5

capacity=5

limit=5

1.2.3flip()

向缓冲区写入数据之后,是否可以直接从缓冲区读取数据呢?不能!这时缓冲区还处于写模式,如果需要读取数据,要将缓冲区转换成读模式。

Test

publicvoidtestFlip(){

IntBufferbuffer=IntBuffer.allocate(20);

buffer.put(1);

buffer.put(2);

buffer.put(3);

buffer.put(4);

buffer.put(5);

System.out.println(调用flip之前);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

//调用

buffer.flip();

System.out.println(调用flip之后);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

}

输出:

调用flip之前

position=5;capacity=20;limit=20

调用flip之后

position=0;capacity=20;limit=5

调用flip()方法后,新模式下可读上限limit的值变成了之前写模式下的position属性值,也就是5;而新的读模式下的position值简单粗暴地变成了0,表示从头开始读取。

对flip()方法从写入到读取转换的规则,:

设置可读上限limit的属性值。将写模式下的缓冲区中内容的最后写入位置position值作为读模式下的limit上限值。其次,把读的起始位置position的值设为0,表示从头开始读。最后,清除之前的mark标记,因为mark保存的是写模式下的临时位置,发生模式翻转后,如果继续使用旧的mark标记,就会造成位置混乱。源码也很简单就是干了这三件事

publicfinalBufferflip(){

//设置可读上限limit,设置为写模式下的position值

limit=position;

//把读的起始位置position的值设为0,表示从头开始读

position=0;

//清除之前的mark标记

mark=-1;

//返回当前实例

returnthis;

}

1.2.4get方法:读取数据

get()方法每次从position的位置读取一个数据,并且进行相应的缓冲区属性的调整。

Test

publicvoidtestGet1(){

IntBufferbuffer=IntBuffer.allocate(20);

System.out.println(刚初始化buffer:);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

buffer.put(1);

buffer.put(2);

buffer.put(3);

buffer.put(4);

buffer.put(5);

System.out.println(调用flip之前:);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

buffer.flip();

System.out.println(调用flip之后:);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

inti=buffer.get();

System.out.println(调用get之后:);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

}

输出:

刚初始化buffer:

position=0;capacity=20;limit=20

调用flip之前:

position=5;capacity=20;limit=20

调用flip之后:

position=0;capacity=20;limit=5

调用get之后:

position=1;capacity=20;limit=5

分析:

声明的buffer的capacity是20个数据,此时position=0;capacity=20放了5个数据之后,position就从零一定移动到了5调用flip之后,准备读取数据,就会把position置为0,limit置为刚刚的5(因为现在只有5个数据可读)调用get读取数据(读取出当前position位置的数据,此时就是0位置出的数据那就是1),position就从0移动了1个位置,position=1了读取操作会改变可读位置position的属性值,而可读上限limit值并不会改变。在position值和limit值相等时,表示所有数据读取完成,position指向了一个没有数据的元素位置,已经不能再读了,此时再读就会抛出BufferUnderflowException异常。

get还有一个重载方法,可以读取指定索引的数据,但是这个方法不会移动position

Test

publicvoidtestGet2(){

IntBufferbuffer=IntBuffer.allocate(20);

System.out.println(刚初始化buffer:);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

buffer.put(1);

buffer.put(2);

buffer.put(3);

buffer.put(4);

buffer.put(5);

System.out.println(调用flip之前:);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

buffer.flip();

System.out.println(调用flip之后:);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

inti=buffer.get(2);

System.out.println(调用get之后,读取的数据是:+i);

System.out.println(position=+buffer.position()+;capacity=+buffer.capacity()+;limit=+buffer.limit());

}

输出

刚初始化buffer:

position=0;capacity=20;limit=20

调用flip之前:

position=5;capacity=20;limit=20

调用flip之后:

position=0;capacity=20;limit=5

调用get之后,读取的数据是:3

position=0;capacity=20;limit=5

此时position没有移动,读取出的数据就是的position为2的数据也就是3

1.2.5clear()清空/

1
查看完整版本: 清华教授手抄NIOBuffer类与属性