java nio之缓冲区(buffer)

1.java的nio采用的是多路复用模型。以下是对各个io模型的简介。

-同步阻塞IO模型:首先,解释一下阻塞与非阻塞。阻塞IO指的是需要内核IO操作彻底完成后才返回到用户空间执行用户程序的操作指令。“阻塞”指的是用户程序(发起IO请求的进程或者线程)的执行状态。可以说传统的IO模型都是阻塞IO模型,并且在Java中默认创建的socket都属于阻塞IO模型。其次,解释一下同步与异步。简单来说,可以将同步与异步看成发起IO请求的两种方式。同步IO是指用户空间(进程或者线程)是主动发起IO请求的一方,系统内核是被动接收方。异步IO则反过来,系统内核是主动发起IO请求的一方,用户空间是被动接收方。同步阻塞IO(Blocking IO)指的是用户空间(或者线程)主动发起,需要等待内核IO操作彻底完成后才返回到用户空间的IO操作。在IO操作过程中,发起IO请求的用户进程(或者线程)处于阻塞状态。

-同步非阻塞模型:非阻塞IO(Non-Blocking IO,NIO)指的是用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间去执行后续的指令,即发起IO请求的用户进程(或者线程)处于非阻塞状态,与此同时,内核会立即返回给用户一个IO状态值。阻塞和非阻塞的区别是什么呢?阻塞是指用户进程(或者线程)一直在等待,而不能做别的事情;非阻塞是指用户进程(或者线程)获得内核返回的状态值就返回自己的空间,可以去做别的事情。在Java中,非阻塞IO的socket被设置为NONBLOCK模式。

-IO多路复用模型:为了提高性能,操作系统引入了一种新的系统调用,专门用于查询IO文件描述符(含socket连接)的就绪状态。在Linux系统中,新的系统调用为select/epoll系统调用。通过该系统调用,一个用户进程(或者线程)可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核就能够将文件描述符的就绪状态返回给用户进程(或者线程),用户空间可以根据文件描述符的就绪状态进行相应的IO系统调用。IO多路复用(IO Multiplexing)属于一种经典的Reactor模式实现,有时也称为异步阻塞IO,Java中的Selector属于这种模型。

-异步IO模型:异步IO(Asynchronous IO,AIO)指的是用户空间的线程变成被动接收者,而内核空间成为主动调用者。在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕并放在了用户缓冲区内,内核在IO完成后通知用户线程直接使用即可。异步IO类似于Java中典型的回调模式,用户进程(或者线程)向内核空间注册了各种IO事件的回调函数,由内核去主动调用。

Buffer:属于缓冲区,数据一般从通道中会读入缓冲区,然后从缓冲区也可以写入到通道。所以对于数据的读写缓冲区功不可没,buffer有一系列的缓冲区子类。

capacity属性Buffer类的capacity属性表示内部容量的大小。一旦写入的对象数量超过了capacity,缓冲区就满了,不能再写入了。Buffer类的capacity属性一旦初始化,就不能再改变。原因是什么呢?Buffer类的对象在初始化时会按照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,最大可写值为limit-1。当position值达到limit时,缓冲区就已经无空间可写了。

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

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

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

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

Buffer的读写模式具体如何切换呢?

当新建了一个缓冲区实例时,缓冲区处于写模式,这时是可以写数据的。在数据写入完成后,如果要从缓冲区读取数据,就要进行模式的切换,可以调用flip()方法将缓冲区变成读模式,flip为翻转的意思。在从写模式到读模式的翻转过程中,position和limit属性值会进行调整,具体的规则是:

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

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

 

limit属性:Buffer类的limit属性表示可以写入或者读取的数据最大上限,其属性值的具体含义也与缓冲区的读写模式有关。在不同的模式下,limit值的含义是不同的,具体分为以下两种情况:

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

(2)在读模式下,limit值的含义为最多能从缓冲区读取多少数据。一般来说,在进行缓冲区操作时是先写入再读取的。当缓冲区写入完成后,就可以开始从Buffer读取数据,调用flip()方法(翻转),这时limit的值也会进行调整。具体如何调整呢?将写模式下的position值设置成读模式下的limit值,也就是说,将之前写入的最大数量作为可以读取数据的上限值。Buffer在翻转时的属性值调整主要涉及position、limit两个属性,但是这种调整比较微妙,不是太好理解,下面举一个简单的例子:首先,创建缓冲区。新创建的缓冲区处于写模式,其position值为0,limit值为最大容量capacity。

然后,向缓冲区写数据。每写入一个数据,position向后面移动一个位置,也就是position的值加1。这里假定写入了5个数,当写入完成后,position的值为5。最后,使用flip方法将缓冲区切换到读模式。limit的值会先被设置成写模式时的position值,所以新的limit值是5,表示可以读取数据的最大上限是5。之后调整position值,新的position会被重置为0,表示可以从0开始读。缓冲区切换到读模式后就可以从缓冲区读取数据了,一直到缓冲区的数据读取完毕。除了以上capacity、position、limit三个重要的成员属性之外,Buffer还有一个比较重要的标记属性:mark(标记)属性。该属性的大致作用为:在缓冲区操作过程当中,可以将当前的position值临时存入mark属性中;需要的时候,再从mark中取出暂存的标记值,恢复到position属性中,重新从position位置开始处理。

java.nio.Buffer类的子类有:

  • ByteBuffer
  • MappedByteBuffer(byte类型)
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

创建类:Buffer buf = CharBuffer.allocate(输入缓冲区容量,一旦输入就固定死了);//创建后默认是写入模式。

子类方法:

allocate():表示创建一个缓冲对象。

put():表示往缓冲区写入数据。

flip():翻转表示将从写入模式翻转到读取模式,注意:此处翻转只是翻转一下只能从写入翻转至读取,并不能翻来翻去。

get():写入模式下读取数据,等读取超过了缓冲区的数据上限时会抛出一个BufferUnderflowException异常。所以需要知道当前是不是到头了。

capacity():缓冲区容量大小,写入的时候注意别超过了,可调用此方法查看。

limit():缓冲区数据大小,读取时可通过此方法查看可读大小。

position():当前读取的位置。以此搭配limit()可得知当前是否读取到最大了。

rewind():倒带。表示已经读过一边了,但想重新再读一边,调用此方法即可。

mark():临时保存。再读取过程中只要调用一下此方法,那么此方法就会记录当前位置。

reset():重置。调用此方法会将当前位置重置到mark()方法保存的位置。例如再缓冲区共有10个元素,当读取到第5个元素时调用了mark(),那么在将来某个时刻调用reset()方法就会将索引重置到mark方法保存的地方。

clear():表示从读模式切换到写模式。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章