Java NIO类库的三个核心组件
Channel
Buffer
Selector
1、OIO(旧的IO)面向流,NIO面向缓冲区,面向字节流或者字符流的IO操作总是以流的方式顺序的从一个流程读取一个或者多个字节,不能随意改变读取指针的位置。在面向缓冲区的NIO中,读取和写入只需要从通道中读取数据到缓冲区或者从缓冲区写入通道,可以随意读取Buffer任意位置数据。
2、OIO操作是阻塞的,而NIO的操作是非阻塞的
3、OIO没有选择器(Selector)概念,NIO存在选择器
4、NIO的实现是基于底层选择器的系统调用的,所以NIO需要底层操作系统提供支持,而OIO则不需要
通道Channel
在OIO中,同一个网络连接会关联到输入流和输出流,在NIO中,一个网络连接使用一个通道表示,所有NIO的IO操作都是通过连接通道完成,一个通道类似于OIO两个流的结合体,既可以从通道读取数据,也可以向通道写入数据
选择器Selector
选择器可以理解为一个IO时间的监听与查询器,通过选择器,一个线程可以查询多个通道的IO事件的就绪状态。一个选择器只需要一个线程进行监控,一个选择器可以管理多个通道
IO多路复用编程的第一步是把通道注册到选择器中,第二步通过选择器所提供的select方法查询这些注册的通道是否有已经就绪的IO事件(可读、可写、网络连接完成等)。
缓冲区Buffer
应用程序与通道的交互主要是进行数据的读取和写入。通道的读取为将数据从通道中读取到缓冲区,数据的写入为将数据从缓冲区写入通道。
NIO中的Buffer内部是一个内存块,既可以写入数据,也可以从中读取数据。非线程安全,有8种缓冲区类,分别是ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MappedByteBuffer,使用最多的为ByteBuffer类。
Buffer内部存在一个数组,但并不在该类中,而是存在于其子类中,不同的子类,数组类型不同。
Buffer类存在三个重要的成员属性:capacity、position,limit
capacity:表示内部容量的大小,一旦写入的对象数量超过了capacity,缓冲区就写满了,不能再写入。capacity一旦被初始化,就不能够改变,原因为Buffer类在对象初始化时会按照capacity分配内部数组的内存,在数组内存分配好之后,大小就不能够再改变。
注:capacity并不是内部内存块byte[]数组的字节数量,而是指写入的数据对象的最大限制数量。
position:表示当前的位置。其值与缓冲区的读写模式有关。
写模式下
1、刚进入写模式,position为0,表示当前的写入位置从头开始
2、每当一个数据写到缓冲区之后,position会向后移动到下一个可写位置
3、初始position为0,最大值为limit-1,当position到达limit时,缓冲区就无数据可写
读模式下:
1、刚进入读模式,position被重置为0
2、当从缓冲区读取数据时,也就是从position开始读取,没读取一个数据,position向前移动到下一个刻度位置
3、读模式下,limit表示可读数据的上限。position的最大值为最大可读上限limit。
新建一个缓冲区实例时,缓冲区处于写模式,写完数据后,可以通过调用flip()方法将缓冲区变为读模式。
limit:表示写入或者读取的数据的最大上限,不同的模式下,limit含义不同
1、写模式下,limit表示写入数据的最大上限,刚进入写模式,limit值会被设置成缓冲区的capacity的值。
2、读模式下,limit标志可以从缓冲区读取的最大字节数
从写模式到读模式,position和limit属性会发生变化
1、limit的值被设置为写模式下position的值
2、position的值重置为0,表示可以从头开始读取
除了以上三个属性,Buffer还存在一个比较重要的属性:mark,作用:在缓冲区操作过程中,可以将当前的position值临时存入mark属性中;需要的时候,再从mark中取出暂存的标记,恢复到position中,重新从position位置开始处理。
buffer类重要方法
allocate方法
buffer类通过其子类的allocate()方法来获取实例
public static void bufferCreate() { Buffer buffer = ByteBuffer.allocate(10); logger.info("position: {}", buffer.position()); logger.info("capacity: {}", buffer.capacity()); logger.info("limit: {}", buffer.limit()); }
14:48:30.385 [main] INFO com.demo.niodemo.NioBuffer - position: 0 14:48:30.389 [main] INFO com.demo.niodemo.NioBuffer - capacity: 10 14:48:30.389 [main] INFO com.demo.niodemo.NioBuffer - limit: 10
从以上可以看出,新建Buffer缓冲区,position为0,capacity和limit相同,都为最大值
put方法
public static void bufferPut() { ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put("abc".getBytes()); logger.info("position: {}", buffer.position()); logger.info("capacity: {}", buffer.capacity()); logger.info("limit: {}", buffer.limit()); }
14:55:47.909 [main] INFO com.demo.niodemo.NioBuffer - position: 3 14:55:47.913 [main] INFO com.demo.niodemo.NioBuffer - capacity: 10 14:55:47.913 [main] INFO com.demo.niodemo.NioBuffer - limit: 10
放入三个字节后,position变为3
flip方法
向缓冲区添加的数据,不能被立即读取,需要使用flip方法进行翻转
public static void bufferFlip() { ByteBuffer buffer = ByteBuffer.allocate(10); buffer.put("abc".getBytes()); buffer.flip(); logger.info("position: {}", buffer.position()); logger.info("capacity: {}", buffer.capacity()); logger.info("limit: {}", buffer.limit()); }
14:59:52.458 [main] INFO com.demo.niodemo.NioBuffer - position: 0 14:59:52.462 [main] INFO com.demo.niodemo.NioBuffer - capacity: 10 14:59:52.462 [main] INFO com.demo.niodemo.NioBuffer - limit: 3
翻转后,position的位置变为0,capacity没有变化,limit的值变为3,为翻转之前position的值,意为最大可以读取到三个字节,如果尝试读取第四个或者以后的字节,则会出现java.nio.BufferUnderflowException异常
clear方法/compact方法
public static void bufferClear() { ByteBuffer buffer = ByteBuffer.allocate(10); byte[] bytes = "abc".getBytes(); buffer.put(bytes); buffer.flip(); for (int i = 0; i < bytes.length; i++) { buffer.get(); } buffer.clear(); logger.info("position: {}", buffer.position()); logger.info("capacity: {}", buffer.capacity()); logger.info("limit: {}", buffer.limit()); }
15:07:33.138 [main] INFO com.demo.niodemo.NioBuffer - position: 0 15:07:33.142 [main] INFO com.demo.niodemo.NioBuffer - capacity: 10 15:07:33.142 [main] INFO com.demo.niodemo.NioBuffer - limit: 10
clear之后,又回到了写状态,此时可以再次向缓冲区添加数据,上述代码中如果不进行get获取,直接进行clear也是可以的。
get方法
get方法用于获取缓冲区数据,如clear方法中介绍,缓冲区数据被读完之后,可以重复读取,需要执行rewind方法
rewind方法
该方法主要是调整了缓冲区的position属性用户mark属性
1、position重置为0
2、limit保持不变
3、mark被清理
其与flip方法很相似,区别在与该方法不会修改limit属性值,而flip方法会将limit属性值设置为写模式下的position值
public static void bufferRewind() { ByteBuffer buffer = ByteBuffer.allocate(10); byte[] bytes = "abc".getBytes(); buffer.put(bytes); buffer.flip(); for (int i = 0; i < bytes.length; i++) { buffer.get(); } buffer.rewind(); for (int i = 0; i < bytes.length; i++) { buffer.get(); } logger.info("position: {}", buffer.position()); logger.info("capacity: {}", buffer.capacity()); logger.info("limit: {}", buffer.limit()); }
mark和reset方法
这两个方法一般一起使用,mark方法将当前position的值保存起来放在mark属性中,让mark属性记住这个临时位置,reset方法将mark的值恢复到position中
public static void bufferMarkReset() { ByteBuffer buffer = ByteBuffer.allocate(10); byte[] bytes = "abc".getBytes(); buffer.put(bytes); buffer.flip(); for (int i = 0; i < bytes.length; i++) { logger.info(String.valueOf(buffer.get())); if (i == 1) { buffer.mark(); } } buffer.reset(); logger.info("*** after mark and reset ***"); logger.info(String.valueOf(buffer.get())); }
16:15:57.533 [main] INFO com.demo.niodemo.NioBuffer - 97 16:15:57.536 [main] INFO com.demo.niodemo.NioBuffer - 98 16:15:57.536 [main] INFO com.demo.niodemo.NioBuffer - 99 16:15:57.536 [main] INFO com.demo.niodemo.NioBuffer - *** after mark and reset *** 16:15:57.536 [main] INFO com.demo.niodemo.NioBuffer - 99
结果中97,98,99分别为a,b,c,此处不再进行转换,从结果可以看出,当读取第二个数字时,调用mark方法记住了这个位置,全部读取结束后,调用reset方法将这个位置回复到position,之后再次通过get方法获取到第三个值。
使用Buffer步骤
1、调用子类allocate方法创建一个子类的Buffer实例
2、调用put方法将数据写入缓冲区
3、写入完成后,调用flip方法将写模式切换为读模式
4、调用get方法获取数据
5、读取数据完成后,调用clear或者compact方法,将缓冲区切换成写模式