NIO
介绍
NIO是当下非常火热的一种IO工作方式,它能够解决传统BIO的痛点:阻塞。
NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
同步非阻塞式IO以块的方式处理数据,面向缓存区的,采用多路复,Reactor模式,基于事件驱动。Netty是实现了NIO的一个流行框架,JBoss的。
- BIO如果遇到IO阻塞时,线程将会被挂起,直到IO完成后才唤醒线程,线程切换带来了额外的开销。
- BIO中每个IO都需要有对应的一个线程去专门处理该次IO请求,会让服务器的压力迅速提高。
NIO与BIO的区别
BIO是面向流的IO,它建立的通道都是单向的,所以输入和输出流的通道不相同,必须建立2个通道,通道内的都是传输==0101001···==的字节数据。
NIO是面向缓冲区的,它会建立一个通道(Channel),该通道我们可以理解为铁路,该铁路上可以运输各种货物,而通道上会有一个缓冲区(Buffer)用于存储真正的数据,缓冲区我们可以理解为一辆火车。通道(铁路)只是作为运输数据的一个连接资源,而真正存储数据的是缓冲区(火车)。即通道负责传输,缓冲区负责存储。
区别基本可以用下表表示:
BIO | NIO |
---|---|
面向流 | 面向缓冲区 |
单向通道 | 双向通道 |
阻塞IO | 非阻塞IO |
— | 选择器Selectors |
概念介绍
缓冲区(Buffer)
缓冲区是存储数据的区域,在 Java 中,缓冲区就是数组,为了可以操作不同数据类型的数据,Java 提供了许多不同类型的缓冲区,除了布尔类型以外,其它基本数据类型都有对应的缓冲区数组对象。
因为NIO是基于缓冲的,所以buffer是最底层的必要类,这也是IO和NIO的根本不同,虽然stream等有buffer开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而NIO却是直接读到buffer中进行操作。
通道(Channel)
类似于IO的stream,但是不同的是除了FileChannel,其他的channel都能以非阻塞状态运行。FileChannel执行的是文件的操作,可以直接DMA操作内存而不依赖于CPU。其他比如socketchannel就可以在数据准备好时才进行调用。IO分为磁盘IO和网络IO,所以通常意义上的NIO是指网络IO,即除FileChnnel之外的channel都可以非阻塞。
通道是可以双向读写的,传统的 BIO 需要使用输入/输出流表示数据的流向,在 NIO 中可以减少通道资源的消耗。
可以通过 getChannel() 方法获取一个通道,支持获取通道的类如下:
- 文件IO:FileInputStream、FileOutputStream、RandomAccessFile
- TCP网络IO:Socket、ServerSocket
- UDP网络IO:DatagramSocket
选择器(Selectors)
选择器是提升IO性能的灵魂之一,它底层利用了多路复用IO机制,让选择器可以监听多个IO连接,根据IO的状态响应到服务器端进行处理。通俗地说:选择器可以监听多个IO连接,而传统的BIO每个IO连接都需要有一个线程去监听和处理。
在BIO中,每个Socket都需要有一个专门的线程去处理每个请求,而在NIO中,只需要一个Selector即可监听各个Socket请求,而且Selector并不是阻塞的,所以不会因为多个线程之间切换导致上下文切换带来的开销。
在Java NIO中,选择器是使用Selector类表示,Selector可以接收各种IO连接,在IO状态准备就绪时,会通知该通道注册的 Selector,Selector在下一次轮询时会发现该IO连接就绪,进而处理该连接,Selector选择器主要用于网络IO当中。
选择器的作用就是:监听多个IO通道,当有通道就绪时选择器会轮询发现该通道,并做相应的处理。那么IO状态分为很多种,我们如何去识别就绪的通道是处于哪种状态呢?在Java中提供了选择键(SelectionKey)。
选择键(SelectionKey)
在选择器轮询到有就绪通道时,会返回这些通道的就绪选择键(SelectionKey),通过选择键可以获取到通道进行操作。
java中提供了四种选择键:
- SelectionKey.OP_READ:套接字通道准备好进行读操作
- SelectionKey.OP_WRITE:套接字通道准备好进行写操作
- SelectionKey.OP_ACCEPT:服务器套接字通道接受其它通道
- SelectionKey.OP_CONNECT:套接字通道准备完成连接