Java中的DLC——NIO系列(一):总览

2022-04-11 17:34:05 浏览数 (1)

“人生苦短,不如养狗 作者:Brucebat.Sun ”

一、概要

  一些热门游戏经常会在一周目结束后推出DLC(Downloadable Content)内容来对现有内容进行扩展和增补。而在JDK的升级发展过程中也经常做出相似的操作,比如IO类库的DLC——NIO(new IO)。

  在”那些年你学了又忘的Java IO“系列中,我们较为系统地回顾了JDK中关于IO相关的操作类库。但是在实际使用的过程中我们会发现,这些IO类库并不能完全满足我们的需求或者性能上并不能尽如人意,具体如下:

  • 传统IO类库在进行数据读写过程中当前线程会阻塞等待数据读入或者写入完毕才可以进行后续的操作,造成了资源空耗(CPU时间的浪费);
  • 在传统IO类库的IO事件触发和处理过程,IO操作和处理器是耦合在一起的,并没有较好的符合单一职责;
  • 在传统IO类库中只提供了基于字节维度的数据读写,面对块维度的IO操作则无能为力;

  为了解决上述的问题,JDK在1.4版本以后引入了一个新的IO类库NIO来对原有的IO类库进行扩展和升级。

二、基本概念

  NIO的整体设计和原始IO相比发生了非常大的转变,从原有实现基本的IO操作到实现IO操作的效率提升。为了达成IO操作效率提升的目标,NIO当中引入以下三个重要的概念:

  • Channel : 数据传输的通道,和传统IO中流(Stream)的概念类似。需要注意流是单向的,而Channel是双向的,即Channel即可以用于读取数据,也可以用于写入数据;
  • Buffer : 缓冲区,用于存储特定类型数据的容器,且这是一个线性、容量有限的容器,这是NIO当中新引入的一个概念。在NIO当中Channel在进行数据读写时都必须面向Buffer进行操作,即进行读取操作时,数据需要先从Channel中读出到Buffer然后才能进行操作,进行写入操作时,数据需要先写入到Buffer当中然后才能通过Channel写入到对应的目标位置;
  • Selector : 多路复用器,和Buffer一样,Selector也是NIO新引入的一个概念,而且是非常重要的一个概念。通过多路复用器,NIO可以监听和处理注册在多路复用器上的Channel的读或写事件,并且一个多路复用器可以管理多个Channel。需要注意的是,在官方注释中多路复用器可以处理的对象只有SelectableChannel及其子类;

  下图中展示了Channel和Buffer在数据传输过程中关系和数据传输的流向:

  撇开缓冲区Buffer暂时不看,这里我们简单看一下NIO中提供的主要Channel类型:FileChannelDatagramChannelSocketChannel以及ServerSocketChannel。从名称和注释文档中可以看出,NIO将主要关注点都放在文件IO和网络上面,前者主要是为了对传统IO中文件IO功能进行优化和增补,而后者则是为了弥补传统IO在网络方面能力的缺失。

  除了在数据处理上的优化增强,NIO还引入了多路复用器Selector来实现非阻塞式IO操作处理。下图简单展示了多路复用器的处理逻辑:

  从上图可以看到,为了实现非阻塞式的IO操作,NIO当中提供了将执行IO操作的Channel注册到Selector的能力,需要注意这里的Channel必须是SelectableChannel及其子类(也就是能够设置成非阻塞模式的Channel)。在Channel注册完毕之后,Selector内部会维护一个SelectionKey(用于标识注册在当前多路复用器上的Channel)的集合,然后通过该集合不断轮询注册在其上的Channel状态,寻找其中已经处于就绪状态(即收到读事件/写事件)的Channel然后发送给执行器进行对应的操作。同时,我们可以发现这里的多路复用器只会存在于一个线程中,无须开辟多个线程进行处理。

  和传统IO相比,引入了多路复用器的NIO极大地减少了线程开销,避免了多线程情况下的上下文切换问题。除此以外,使用多路复用实现的非阻塞IO操作极大提升了资源使用的效率,避免了阻塞式IO线程空闲等待的问题。

三、总结

  新的NIO类库并不是对传统IO类库的全盘否定,而是在传统IO基础上进行补充和优化,比如优化和解决文章开头提出的一些实际应用中的问题。在后续的学习中,我们会以面向应用场景和横向对比的方式去具体了解NIO的设计理念以及使用方法。

0 人点赞