前言
我们知道,Java中的NIO实际上使用的是多种IO模型中的IO多路复用策略,在NIO中,引入了Buffer缓冲区,Channel通道,Selector选择器三个概念,现在先看一下Buffer缓冲区的一些基本知识。
介绍
NIO的Buffer本质上是一个内存块,既可以写入数据,也可以从中读取数据,Java NIO中代表缓冲区的Buffer类是一个抽象类,位于java.nio包中。
NIO的内部是一个内存块(数组),与普通的内存块(Java数组)不同的是,NIO Buffer对象提供了一系列有效地方法,用来进行写入和读取的交替访问。
在NIO中,有8种缓冲区类型,分别是 ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer,MappedByteBuffer。前7种Buffer类型覆盖了能在IO中传输的所有Java基本数据类型,第8种类型是一种专门用于内存映射的ByteBuffer类型,不同的Buffer子类可以操作的数据类型能够通过名称进行判断,比如IntBuffer只能操作Integer类型的对象。
属性
为了记录读写的状态和位置,Buffer类额外提供了一些重要的属性,其中有四个十分重要的成员属性capacity,position,limit,mark。
capacity:容量
capacity属性表示Buffer内部容量的大小,一旦写入对象的数量超过了capacity,缓冲区满了,就不能再写入了。
capacity一旦初始化,就不能再改变,原因是Buffer类的对象在初始化的时候会按照capacity分配内部数组的内存,在数组内存被分配完毕后,就不能再改变大小了。
capacity并不是指内部内存块byte[]数组的字节数量,而是指能写入的数据对象的限制数量。
position:位置
position属性的值和缓冲区的读写模式有关,在不同的模式下,position属性值的含义是不同的,当缓冲区的读写模式发生改变时,position值会进行相应的调整。
写模式
- 在刚进入写模式时,position的值为0,表示当前写入的位置从0开始。
- 当一个数据写入缓冲区之后,position的位置会向后移动到下一个可写的位置。
- 初始position值为0,最大可写值为limit-1,当position的值达到limit时,表示缓冲区已经无位置可写了。
读模式
- 当缓冲区开始进入读模式时,position的值会被重置为0。
- 当从缓冲区开始读取数据的时候,也是从position位置开始读,读取数据后,position的位置会向后移动到下一个可读的位置。
- 在读模式下,limit表示可读数据的上限,position的最大值为最大可读上限limit,当position的位置达到limit,表示缓冲区已无数据可读。
Buffer的读写切换
当新建了一个缓冲区示例后,缓冲区默认处于写模式,在数据写入完毕之后,可以使用**flip()**方法将缓冲区切换为读模式。
在从写模式切换到读模式的过程中,position和limit的属性值会发生变化,limit的值设置为写模式时的position的值,表示可以读取的最大数据的位置,position由原来的写入位置变为新的可读位置,也就是0,表示可以从头开始读取。
limit:上限
limit属性表示可以写入或者读取的最大位置,具体含义也和读写的模式有关。
写模式
在写模式下,初始化时limit的值会被设置为缓冲区的capacity值,表示可以将缓冲区的容量写满。
读模式
在读模式下,limit的值被设置为写模式下的position的值,表示最多能从缓冲区中读取多少数据。
mark:标记
在缓冲区的操作过程中,可以将当前的position值临时存入mark属性中,在需要的时候,再从mark中取出暂存的标记值,恢复到position位置开始处理。
方法
allocate()
在使用Buffer实例之前,我们需要先获取Buffer子类的实例对象,并且分配内存空间,在获取实例对象时,并不是使用子类的构造器来创建,而是使用子类的allocate()方法。
![在这里插入图片描述](https://img-blog.csdnimg.cn/f6300e907aa54aaab593465e69f66ee4.gif#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/d57321f870504b64925f1623bc249e9d.gif#pic_center)
从实例中可以看出,一个缓冲区在创建之后默认是处于写模式的,position的属性值为0,capacity和limit的属性值相同,都为20。
put()
在缓冲区创建完毕后,就可以写入对象,如果要把对象写入到缓冲区中,就需要调用put()方法,要求写入的数据类型和缓冲区的数据类型保持一致。
![在这里插入图片描述](https://img-blog.csdnimg.cn/b4d69a87cb7a467a958849f234a23b78.gif#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/8a853f332c484ecc805a8693f24a047a.gif#pic_center)
从结果可以看出,写入了5个元素之后,position的属性值就变为了5,指向了第6个可以写入的位置,而capacity和limit两个属性并没有发生变化。
flip()
在缓冲区写入完数据之后,还不能从中直接读取数据,需要将缓冲区切换为读模式,使用**flip()**翻转方法。
![在这里插入图片描述](https://img-blog.csdnimg.cn/625ce1ab187d4bba8b0f3cf61a177202.gif#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/6aae13fe16d14ea09a964c9202490a90.gif#pic_center)
在翻转之后,position和limit属性的值都发生了变化,limit变为了写模式下position的值,表示最大的读取位置是5,而position的值变为0,表示从头开始读取。最后,清楚mark的值,因为mark储存的是写模式下的临时位置,发生翻转之后需要清空,防止发生混乱。
可以看一下源码:
![在这里插入图片描述](https://img-blog.csdnimg.cn/d6d81d7ad5db44faa34014fad24da1fc.gif#pic_center)
那当数据读取完毕之后,如何从读模式切换到写模式呢,可以调用clear()方法清空或者调用compact()方法进行压缩。
get()
切换到读模式之后,就可以从缓冲区读取数据了,可以调用get()方法读取数据。并且缓冲区的属性也会相应的发生变化。
![在这里插入图片描述](https://img-blog.csdnimg.cn/4f221dc6252c495ebf398c222482ecd6.gif#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/9ee54a6fe1e5483d84f0b90464f18d33.gif#pic_center)
从以上实例可以看出,读操作会改变position的值,而不会改变limit的值,当position的值和limit的值相等时,表示所有数据读取完成,如果继续读取,则会抛出BufferUnderflowException异常。
在读取完毕之后是不能立即写入数据的,必需要调用clear()方法清空或者调用compact()方法进行压缩。
那么已经读取完的缓冲区是否可以重复读呢,答案是可以的,需要使用rewind()方法。
rewind()
rewind()也叫倒带,就像磁带一样,倒回去重新听一遍。
![在这里插入图片描述](https://img-blog.csdnimg.cn/89aafe4e7e7c4b6197e121cd3c22d6be.gif#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/9ceeea0aa36d499a96df816ccaae2e8b.gif#pic_center)
rewind()方法主要是调整了缓冲区的position和mark属性的值。
将position设置为0,limit保持不变,mark被清理,清除掉之前存储的位置。
![在这里插入图片描述](https://img-blog.csdnimg.cn/722c86c8c6f44e909eed6d2fd653d3cd.gif#pic_center)
mark()和reset()
这两个方法是配套使用的,mark()方法将当前position设置到mark属性中,reset()将mark属性的值恢复到position中,以便于从这个位置重复调用。
![在这里插入图片描述](https://img-blog.csdnimg.cn/d56d5c2be2104cbea6f3ff7a5aff678e.gif#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/e9f323fb00344fe1a0ca443437599017.gif#pic_center)
使用reset()方法之后,position的值为2,此时再去读取缓冲区,得到的结果为2,3,4。
clear()
在读模式下,调用clear()方法可以将缓冲区从读模式切换为写模式。
此时position的值被清零,limit的值变为capacity的值,可以一直写入,知道缓冲区满。
![在这里插入图片描述](https://img-blog.csdnimg.cn/01221a92d17c40daa4a66ca149a95043.gif#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/23ab42febad14b09a02866c9ce48fe50.gif#pic_center)
总结
- 使用子类对象的allocate()方法实例化Buffer对象。
- 使用put()方法添加数据。
- 当数据写入完毕后使用flip()方法,切换为读模式。
- 使用get()方法从缓冲区中读取数据。
- 读取完成之后,使用clear()或者compact()方法,将缓冲区从读模式切换为写模式。可以继续写入。
下篇文章总结一下Channel类,如有帮助,请点赞,谢谢!