输入输出是操作系统不可或缺的一部分,大致分为两类:面向磁盘和面向网络。在 Java 中有3种 I/O 类型:BIO、NIO 和 AIO,分别是同步阻塞、同步非阻塞和异步非阻塞 I/O,这里着重描述 BIO 和 NIO 的区别和常用的编程模型。

1. 为什么设计 NIO

一个直接原因就是为了更好的利用操作系统特性,改善和扩展原有 API。与 NIO 相关的规范有两个:

  • JSR 51:它是 NIO 的第一个规范,关注缓冲区、通道和字符集的设计,引入一个简单的面向缓冲区的 I/O 模型,并且提供一套非阻塞、I/O 多路复用、可扩展的 API
  • JSR 203(NIO.2):它在前者的基础上,添加新的文件系统的抽象,完善现有 Socket 通道的配置,添加多播数据报的支持,并且定义了一个异步 I/O 编程 API

那么,传统的 BIO 又有什么弊端?NIO 又是如何改进的?可以从两方面进行说明。

1.2 文件操作

关于 java.io.file,它的不足之处在于:

  • 查询文件属性时,如修改时间或文件类型,都会发生系统调用,并且这些组合操作非常常见,造成性能问题
  • 部分方法在发生错误时返回 false 而不是抛出异常,比如 delete、rename,不知操作失败的原因
  • 一些 OS 高级功能不支持,比如符号链接、文件锁定、内存映射等

而 NIO 支持批量获取文件属性,对文件、目录的处理也重新设计,提供 FileLock、MappedByteBuffer 等支持 OS 高级功能。

1.3 网络通信

BIO 是同步阻塞、基于流的 I/O,阻塞就意味着当 Socket 输入流中无数据可读取时,调用线程挂起,直到有数据读取,期间不能处理其他请求,如果来了一个新连接,就只能再新建一个线程处理。

随着连接数的增加,BIO 将会创建大量线程,而一个计算机能打开的进程数或线程数是有限的,严重的时候可能会导致应用崩溃无响应。一个有效的解决办法是使用线程池,限制最大线程数,但它同时也限制了最大连接数。

NIO 将读写改为非阻塞,无数据可读,线程返回线程池,可用于处理其他连接。它对原始 I/O 提供了新的抽象 - Channel(通道),并且提供基于缓冲区的读写 API。Channel 表示一个到硬件设备、文件或网络套接字的连接,与 java.net.Socket 的区别是:

  • 可配置非阻塞,允许事件驱动的设计,提供了一种更加可扩展的服务器开发
  • 面向缓冲区,可实现零拷贝执行 I/O ,只不过有一端必须是 FileChannel

相同环境下,BIO 的线程全程只处理一条连接,而 NIO 的线程可处理多个连接,提高了系统的吞吐能力。NIO 在服务器进行纵向扩展(比如增加内存、CPU)或者横向扩展(比如增加服务器)往往能够比 BIO 带来更高的处理能力,使服务器具有更强的可扩展性和可伸缩性。

1.4 零拷贝

NIO 还有一个零拷贝的概念,零拷贝是指 CPU 不执行将数据从一个存储区复制到另一个存储区的操作。OS 级别的零拷贝指的是将数据发送到硬件驱动程序(网卡或磁盘驱动器)时避免从一个位置复制到另一个位置(一般是从用户空间到内核空间),反之亦然。NIO 中的零拷贝就是这样,只不过它只针对在网络上发送文件。

2. I/O 模型的选择

一般的,我们潜意识的会认为 NIO 比 BIO 的性能高,其实不尽然,当然了有个读取方式的问题,read(byte[]) 和 read(ByteBuffer)应该没区别吧?所以如果系统的并发量不高,两个用谁都行。

BIO 的问题通常会在海量的连接下体现出来,由于它不能充分利用、压榨一台服务器的性能,不管怎么扩展,它能处理的连接数与机器性能往往是非线性的,付出和收获不成正比。如果你的应用面临的连接不断增加,特别是存在大量的长连接,此时就要选择 NIO,它不仅提高了单机处理能力,还能节省服务器成本。

NIO 相比 BIO 的重点在于可扩展性,在选择 I/O 模型时,需要结合业务场景,综合考虑以下几点:

  • 预计最大的并发数
  • 短连接还是长连接
  • 预计每个连接的数据量,即流量的大小
  • NIO 灵活,但代价是编程复杂

3. 编程模型

BIO 的编程模型是一连接一处理线程,采用线程池优化。

NIO 典型的编程模型是 Reactor,事件复用器通知套接字何时准备好读取和写入操作的事件,将事件传递给合适的处理程序,由该程序负责实际的读取或写入。对于读操作基本过程如下:

  • 处理程序声明感兴趣的 I/O 事件 - 读取事件
  • 事件复用器等待事件
  • 一个事件发生,复用器被唤醒并调用适当的处理程序
  • 处理程序执行实际的读取操作,处理读取的数据,重新声明关注的 I/O 事件,并将控制权返回给调度程序

与 Reactor 相对的还有一个 AIO 的 Proactor 模型,它是异步 I/O,事件复用器等待 I/O 操作完成的事件,它是真正的异步,因为实际的 I/O 操作完全由操作系统执行。对于读操作,它的做法是:

  • 处理程序启动异步读取操作,在这种情况下,处理程序不关心 I/O 就绪事件,而是关注接收完成的事件
  • 事件复用器等待操作完成
  • 当事件复用器等待时,OS 并行的在内核线程中执行读操作,将数据放入用户定义的缓冲区,读取完成后通知事件多路复用器
  • 事件复用器调用适当的处理程序
  • 处理程序处理用户定义缓冲区的数据,启动新的异步操作,并将控制返回给事件多路复用器

4. 小结

I/O 模型经常对比的是阻塞和非阻塞,还有同步和异步,在《UNIX 网络编程》第6章已经给出了它们的区别,并且给出的图示很直观。这里简单做下说明,以 UDP 为例,读取一个数据包可以分为两个阶段:

  • 第一阶段:应用进程发起系统调用,内核无数据包准备好,等待数据
  • 第二阶段:数据包准备好,OS 将数据从内核复制到用户空间

阻塞和非阻塞描述的是第一阶段无数据可读取时线程是否挂起;同步和异步描述的是第二阶段,在数据复制过程中线程是否参与和挂起。注意 NIO/BIO 都是同步 I/O,NIO 对应 UNP 中描述的 I/O 复用模型。

IO 和 NIO 的思考的更多相关文章

  1. 面试题思考:IO 和 NIO的区别,NIO优点

    面试时答: IO是面向流的,NIO是面向缓冲区的 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方: NIO则能前后移动流中的数据,因为是面向缓冲区的 ...

  2. java面试题之----IO与NIO的区别

    JAVA NIO vs IO 当我们学习了Java NIO和IO后,我们很快就会思考一个问题: 什么时候应该使用IO,什么时候我应该使用NIO 在下文中我会尝试用例子阐述java NIO 和IO的区别 ...

  3. IO、NIO实现简单聊天室,附带问题解析

      本篇文章主要使用IO和NIO的形式来实现一个简单的聊天室,并且说明IO方法存在的问题,而NIO又是如何解决的.   大概的框架为,先提供思路和大概框架图--代码--问题及解决方式,这样会容易看一点 ...

  4. java的nio之:java的nio系列教程之java的io和nio的区别

    当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们如何影响您的代 ...

  5. 面试题_66_to_75_Java IO 和 NIO 的面试题

    IO 是 Java 面试中一个非常重要的点.你应该很好掌握 Java IO,NIO,NIO2 以及与操作系统,磁盘 IO 相关的基础知识.下面是 Java IO 中经常问的问题. 66)在我 Java ...

  6. 传统IO与NIO的比较

    本文并非Java.io或Java.nio的使用手册,也不是如何使用Java.io与Java.nio的技术文档.这里只是尝试比较这两个包,用最简单的方式突出它们的区别和各自的特性.Java.nio提出了 ...

  7. Java IO 和 NIO

    昨天面试问到了有关Java NIO的问题,没有答上来.于是,在网上看到了一篇很有用的系列文章讲Java IO的,浅显易懂.后面的备注里有该系列文章的链接.内容不算很长,需要两个小时肯定看完了,将该系列 ...

  8. JAVA中IO和NIO的详解分析,内容来自网络和自己总结

    用一个例子来阐释: 一辆客车上有10个乘客,他们的目的地各不相同,当没有售票员的时候,司机就需要不断的询问每一站是否有乘客需要下车,需要则停下,不需要则继续开车,这种就是阻塞的方式. 当有售票员的时候 ...

  9. Nio学习4——EchoServer在IO,NIO,NIO.2中的实现

    堵塞IO实现: public class PlainEchoServer { public void serve(int port) throws IOException { final Server ...

随机推荐

  1. CocoaPods 安装 使用&常见操作错误

    CocoaPods 安装 使用 1.开启 terminal 2.移除现有 Ruby 默认源 $ gem sources --remove https://rubygems.org/ 3.使用新的源 $ ...

  2. CSS 盒模型与box-sizing

    一.盒模型 一个web页面由许多html元素组成,而每一个html元素都可以表示为一个矩形的盒子,CSS盒模型正是描述这些矩形盒子的存在. MDN的描述: When laying out a docu ...

  3. 已管理员身份从cmd框进入mysql,及常用的简单操作!

    在命令框中操作mysql已管理员的身份进入操作权限较高,已普通用户进入cmd框也可对mysql进行操作,不过一般建议用管理员身份进入. 1.启动MYSQL Notifier 2.已管理员身份进入cmd ...

  4. golang从简单的即时聊天来看架构演变

    前言 俗话说的好,架构从来都不是一蹴而就的,没有什么架构一开始设计就是最终版本,其中需要经过很多步骤的变化,今天我们就从一个最简单的例子来看看,究竟架构这个东西是怎么变的. 我将从一个最简单的聊天室的 ...

  5. 从壹开始微服务 [ DDD ] 之一 ║ D3模式设计初探 与 我的计划书

    缘起 哈喽大家周四好!又是开心的一天,时间过的真快,我们的 <从壹开始 .net core 2.1 + vue 2.5>前后端分离系列共 34 篇已经完结了,当然以后肯定还会有更新和修改, ...

  6. asp.net core系列 55 IS4结合Identity密码保护API

    一.概述 OAuth 2.资源所有者密码授权允许客户端(Client项目)向令牌服务(IdentityServer项目)发送用户名和密码,并获取代表该用户的访问令牌.本篇将IS4结合asp.net c ...

  7. NIO(生活篇)

    今晚是个下雨天,写完今天最后一行代码,小鲁班起身合上电脑,用滚烫的开水为自己泡制了一桶老坛酸菜牛肉面.这大概是苦逼程序猿给接下来继续奋战的自己最好的馈赠.年轻的程序猿更偏爱坐在窗前,在夜晚中静静的享受 ...

  8. windows&lunix下node.js实现模板化生成word文件

    最近在做了一个小程序!里面有个功能就是根据用户提交的数据,自动生成一份word文档返回给用户.我也是第一次做这功能,大概思路就是先自己弄一份word模板,后台接受小程序发过来的数据,再根据这些数据将相 ...

  9. 结合JDK源码看设计模式——观察者模式

    前言: 现在我们生活中已经离不开微信,QQ等交流软件,这对于我们来说不仅是交流,更有在朋友圈中或空间中进行分享自己的生活,同时也可以通过这个渠道知道别人的生活.我们在看朋友圈的时候其实我们扮演的就是一 ...

  10. 004. 前端跨域资源请求: JSONP/CORS/反向代理

    1.什么是跨域资源请求? https://www.cnblogs.com/niuli1987/p/10252214.html 同源: 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有 ...