Java NIO
Java NIO
在整理NIO文档前先介绍下5个I/O模型。在将I/O模型之前还得介绍几个概念:
- 内核空间和用户空间
数据从磁盘移动到用户进程的内存区域时,要涉及到内核空间和用户空间。
用户空间就是常规进程(如JVM)所在区域,_用户空间_是非特权区域
,如不能直接访问硬件设备
。内核空间是操作系统所在区域
,那肯定是有特权啦,如能与设备控制器通讯,控制用户区域的进程运行状态。进程执行I/O操作时,它执行一个系统调用把控制权交由内核。
- 缓冲区操作
缓冲区操作是所有I/O的基础,进程执行I/O操作,归结起来就是向操作系统发出请求,让它要么把缓冲区里的数据排干(写),要么把缓冲区填满(读)
5种I/O模型
IO请求分为两个阶段:1、等待数据就绪。2、从内核缓冲区拷贝数据到进程缓冲区
按照请求是否阻塞分为:同步IO和异步IO。
Unix存在5种IO模型:
(1)阻塞IO:最常用的模型,缺省情况文件操作都是阻塞的。
(2)非阻塞IO:用户需要不断主动询问kernel数据是否准备好了,准备好之后通知用户将数据从内核空间copy到用户空间(用户得到通知之后,亲自去内核空间中将数据复制到用户空间)。
这里的非阻塞是指等待数据准备好的这段时间不会阻塞,需要说明的是等待就绪的阻塞是不使用CPU的,是在空等;而真正的读写操作的阻塞是使用CPU的,真正在”干活”,而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。。
(3)IO复用:也叫作event driven IO
(4)信号驱动IO:内核通知我们何时可以开始一个I/O操作
(5)异步IO:内核通知我们I/O操作何时已经完成
疑问–Java NIO对应哪种I/O模型?
wiki上说”Non-blocking I/O (usually called NIO, and sometimes called “New I/O”)”,而又有很多blog说NIO就是IO复用,经常说NIO的
select
是IO复用,在I/O模型中非阻塞IO和IO复用是两种不同的模型,这个是怎么回事???Java NIO与Non-blocking I/O、I/O复用的关系是什么????是一种同步非阻塞的I/O模型,也是I/O多路复用的基础。
NIO的优势
NIO较于传统IO的优势在于NIO是基于块的,而且有通道(Channel)和缓冲区(buffer)的概念,更加接近操作系统执行IO的方式。
优势如下:
- 事件驱动模型(Reactor设计模式–Reactor是并发编程中的一种基于事件驱动的设计模式。)
- 避免多线程(Reactor模式中_单线程轮询_来进行事件分发)
- 非阻塞I/O,I/O读写不再阻塞,而是返回0
- 基于block的传输,通常比基于流的传输更高效
- 更高级的IO函数,zero-copy
- IO多路复用大大提高了Java网络应用的可伸缩性和实用性
概念
- 缓冲区
在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:
position:指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。
limit:指定当前buffer中读取数据时的末尾index 例如当前buffer的capacity是10,但只写入内容只占到4,则此时读取buffer时,limit为4,读取的内容到limit为止,也可以说limit记录了当前buffer已用的容量。也就是说指定当前buffer有多少数据需要取出(在从缓冲区写入通道时),或者有多少空间可以放入数据(在从通道读入缓冲区时)。
capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。
读取buffer中的数据时,调用
flip()
,此方法将会完成两件事情:
1、把limit设置为当前的position值
2、把position设置为0
以上三个属性值之间相对大小的关系:0 <= position <= limit <= capacity。如果我们创建一个新的容量大小为10的ByteBuffer对象,在初始化的时候(调用clear()
方法将buffer重置为初始化),position设置为0,limit和 capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其它两个个将会随着使用而变化。
- 通道
通道主要用来传输数据,与缓冲区进行交互,与Selector关联起来使用的,实际上是通过SelectableChannel中的register()
方法进行注册的(如果在阻塞模式下注册一个通道,系统会抛出IllegalBlockingModeException异常。 )。register()
方法返回一个SelectionKey
,SelectionKey
是Channel和Selector之间的关联对象,该对象可以通过channel()
和selector()
方法得到该SelectionKey关联的channel和selector。SelectionKey维护着两个Set集合,一个是感兴趣的事件集合interestOps()
,另一个是准备好了的,可以进行IO操作的集合readyOps()
。SelectionKey类中定义了4种可选择操作:read、write、 connect和accept,每个具体通道有不同的有效的可选择操作集 合,比如ServerSocketChannel的有效操作集合是accept,而SocketChannel的有效操作集合是read、write和 connect。
- 选择器
选择器可谓NIO中的重头戏,I/O复用的核心,其中维护着两个键的集合:已注册的键的集合keys()
和已选择的键的集合selectedKeys()
,已选择的键的集合是已注册的键的集合的子集。除此之外,其实选择器内部还维护着一个已取消的键的集合,这个集合包含了cancel()方法被调用过的键。
当Selector和特定的SelectableChannel关联好后就开始工作了,工作时只需要调用select()
方法。select()
是阻塞调用线程,直到有某个Channel的某个感兴趣的Op准备好了。select()
只返回本次执行select()
时从_未准备好到准备好状态的channel数(在执行select()
之前已经准备好的channel不进行计数)_,如果不为0,将调用selectedKeys()
方法得到一个已选择的键的集合set。
代码
下面是一个服务器端代码
1 | int port = 30; |
参考
https://yq.aliyun.com/articles/2371
http://www.molotang.com/articles/906.html
http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf