浅析 NIO
https://blog.****.net/u013068377/article/details/70312551(BIO NIO AIO 区别)
一 NIO和IO的区别
1.IO是面向流的,NIO是面向缓冲区的。
2 NIO的通道是可以双向的,但是IO中的流只能是单向的。
3.IO的各种流是阻塞的。NIO是非阻塞的。
二 NIO简介
NIO的三大组件 Channel Buffer Selector
Channel
国内大多翻译成“通道”。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。
NIO中的Channel的主要实现有:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
这里看名字就可以猜出个所以然来:分别可以对应文件IO、UDP和TCP(Server和Client)。下面演示的案例基本上就是围绕这4个类型的Channel进行陈述的。
Buffer
NIO 中的关键Buffer实现有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应基本数据类型: byte, char, double, float, int, long, short。当然NIO中还有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等这里先不进行陈述。
Selector
Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。例如在一个聊天服务器中。要使用Selector, 得向Selector注册Channel
NIO整体框图:
三 用法
Buffer
控制buffer状态的三个变量:
• position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置
• limit:代表还有多少数据可以取出或还有多少空间可以写入,它的值小于等于capacity。
• capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。
最初位置(未读取数据):
写入一些数据后:
调用buffer.flip() 方法(写模式转换成读模式):
这个方法把当前的指针位置position设置成了limit,再将当前指针position指向数据的最开始端,我们现在可以将数据从缓冲区写入通道了。 position 被设置为 0,这意味着我们得到的下一个字节是第一个字节。 limit 已被设置为原来的 position,这意味着它包括以前读到的所有字节,并且一个字节也不多。
为什么要这样呢?比如需要将缓冲区数据取出来解析(get方法),我现在要读了,就不要再写了,所以调用flip()方法,保存现在状态不再写入数据。
FileInputStream fis = new FileInputStream(resource);
FileOutputStream fos = new FileOutputStream(destination);
FileChannel readChannel = fis.getChannel(); // 读文件通道
FileChannel writeChannel = fos.getChannel(); // 写文件通道
ByteBuffer buffer = ByteBuffer.allocate(1024); // 创建缓存区,并设置其大小
while (true) {
buffer.clear(); //清空缓存区域
int len = readChannel.read(buffer); // 读入数据
if (len == -1) {
break; // 读取完毕
}
buffer.flip(); //缓存区状态转变
writeChannel.write(buffer); // 写入文件
}
readChannel.close();
writeChannel.close();
Selector
用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。
使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。
当有读或者写等任何注册的事件发生时,可以从Selector中获得相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。
使用NIO中非阻塞IO编写服务器处理程序,有三个步骤
1.向Selector对象注册感兴趣的事件
2.从Selector中获取感兴趣的事件
3.根据不同事件进行相应的处理
Selector selector = Selector.open(); //创建 Selector
channel.configureBlocking(false); //设置channel为非租塞
SelectionKey key = channel.register(selector, Selectionkey.OP_READ); //注册到Selector
第二个参数意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:
1. SelectionKey.OP_CONNECT //连接就绪
2. SelectionKey.OP_ACCEPT //接收就绪
3. SelectionKey.OP_READ //读就绪
4. SelectionKey.OP_WRITE //写就绪
关于 SelectionKey 与 通过Selector选择通道 的更多信息:
参考:https://blog.****.net/u011381576/article/details/79876754
反应堆
1)阻塞IO模型
在老的IO包中,serverSocket和socket都是阻塞式的,因此一旦有大规模的并发行为,而每一个访问都会开启一个新线程。这时会有大规模的线程上下文切换操作(因为都在等待,所以资源全都被已有的线程吃掉了),这时无论是等待的线程还是正在处理的线程,响应率都会下降,并且会影响新的线程。
2)NIO
Java NIO是在jdk1.4开始使用的,它既可以说成“新IO”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:
1.由一个专门的线程来处理所有的IO事件,并负责分发。
2.事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3.线程通讯:线程之间通过wait,notify等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
引用: