-
原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的
-
NIO在数据的读取和写入都不是阻塞的
- 从通道读取数据时候可能读到数据也可能读不到数据,读不到数据也不会阻塞
- 写入数据时也不需要阻塞到数据完全写入,只需要将数据交给缓存即可
-
NIO因为线程不阻塞,所以一个线程可以处理多个IO读写事件,阻塞IO的话一个IO请求就要分配一个线程来处理
-
在标准IO API中,你可以操作字节流和字符流,但在新IO中,你可以操作通道和缓冲,数据总是从通道被读取到缓冲中或者从缓冲写入到通道。
-
Java NIO的服务端只需启动一个专门的线程来处理所有的 IO 事件,再将事件分发出去给其他线程处理
-
java NIO采用了双向通道(channel)进行数据传输,而不是单向的流(stream),在通道上可以注册我们感兴趣的事件。一共有以下四种事件:
- 服务端接收客户端连接事件 | SelectionKey.OP_ACCEPT(16)
- 客户端连接服务端事件 | SelectionKey.OP_CONNECT(8)
- 读事件 | SelectionKey.OP_READ(1)
- 写事件 | SelectionKey.OP_WRITE(4)
-
java NIO使用了Selector的机制,可以将自己对某个通道感兴趣的事件注册在Selector上,Selector基于内部的机制,会在产生事件的时候进行通知,我们就可以拿到对应的事件进行处理,Selector有两个重要的实现
-
PollSelectorImpl
这个实现使用了poll的方式实现多路复用选择器,poll会使用轮询的方式检查Sokcet对应事件是否就绪,内部使用链表的方式存储(不确定,源码没找到链表)。
-
EPollSelectorImpl
这个实现使用了epoll的方式实现多路复用选择器,epoll可以监控的文件描述符数量是可以打开文件的数量上限,epoll获取事件不是通过轮询得到,而是通过给每个文件描述符定义回调得到,linux会在对应的事件就绪时候进行回调通知,该实现也是linux的特有实现。
-
-
通过调用selector.select()来得到已经就绪的事件,并对事件进行处理
- select方法内部会进行阻塞,直到有事件返回
- 也可以使用带有超市时间的select方法,会在超时后直接返回
- 返回的是一个可以处理的事件数量
-
通过selector.selectedKeys()获取可以处理的事件集合
- 代表一个个可以处理的事件SelectionKey
- 每个SelectionKey只包含一个事件,同时内部持有对应的通道(Channel),以下四个方法代表上面对应的四个事件的检查,用以检查是否是对应的事件完成
- isAcceptable
- isConnectable
- isWritable
- isReadable
- 从对应的事件中得到通道,可以对通道进行处理,比如接受连接,写入消息,读取消息等等
- 处理过的SelectionKey需要从集合移除,不然会重复处理
-
ByteBuffer
- NIO针对数据的操作都是针对缓存的,和以前针对流不同
- 从通道读取数据需要先将数据读取到缓冲区,然后再操作缓冲区
- 写入数据到通道也需要先将数据写入到缓冲区,然后再操作通道将缓冲区写入到通道
- nio数据的粘包也需要再这里处理,有些包也许一个缓冲区装不下,就需要利用多个缓冲区将包黏在一起,最后形成一个真正的数据包
- 使用ByteBuffer.allocate可以分配一个缓冲空间,缓冲空间有堆空间和直接空间之分,堆空间就是在堆上分配的一个缓冲空间,直接空间就是在外部直接内存上分配的内存空间。
- ByteBuffer内部使用一些下标的标志位来维护数据
网友评论