IO 和 NIO 的思考
输入输出是操作系统不可或缺的一部分,大致分为两类:面向磁盘和面向网络。在 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 的思考的更多相关文章
- 面试题思考:IO 和 NIO的区别,NIO优点
面试时答: IO是面向流的,NIO是面向缓冲区的 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方: NIO则能前后移动流中的数据,因为是面向缓冲区的 ...
- java面试题之----IO与NIO的区别
JAVA NIO vs IO 当我们学习了Java NIO和IO后,我们很快就会思考一个问题: 什么时候应该使用IO,什么时候我应该使用NIO 在下文中我会尝试用例子阐述java NIO 和IO的区别 ...
- IO、NIO实现简单聊天室,附带问题解析
本篇文章主要使用IO和NIO的形式来实现一个简单的聊天室,并且说明IO方法存在的问题,而NIO又是如何解决的. 大概的框架为,先提供思路和大概框架图--代码--问题及解决方式,这样会容易看一点 ...
- java的nio之:java的nio系列教程之java的io和nio的区别
当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们如何影响您的代 ...
- 面试题_66_to_75_Java IO 和 NIO 的面试题
IO 是 Java 面试中一个非常重要的点.你应该很好掌握 Java IO,NIO,NIO2 以及与操作系统,磁盘 IO 相关的基础知识.下面是 Java IO 中经常问的问题. 66)在我 Java ...
- 传统IO与NIO的比较
本文并非Java.io或Java.nio的使用手册,也不是如何使用Java.io与Java.nio的技术文档.这里只是尝试比较这两个包,用最简单的方式突出它们的区别和各自的特性.Java.nio提出了 ...
- Java IO 和 NIO
昨天面试问到了有关Java NIO的问题,没有答上来.于是,在网上看到了一篇很有用的系列文章讲Java IO的,浅显易懂.后面的备注里有该系列文章的链接.内容不算很长,需要两个小时肯定看完了,将该系列 ...
- JAVA中IO和NIO的详解分析,内容来自网络和自己总结
用一个例子来阐释: 一辆客车上有10个乘客,他们的目的地各不相同,当没有售票员的时候,司机就需要不断的询问每一站是否有乘客需要下车,需要则停下,不需要则继续开车,这种就是阻塞的方式. 当有售票员的时候 ...
- Nio学习4——EchoServer在IO,NIO,NIO.2中的实现
堵塞IO实现: public class PlainEchoServer { public void serve(int port) throws IOException { final Server ...
随机推荐
- 【bzoj 3601】一个人的数论 (莫比乌斯反演+伯努利数)
题解: (吐槽:网上题解那个不严谨猜测真是没谁了……关键是还猜得辣么准……) 直接化简到求和那一段: $f_{d}(n)=\sum_{t|n}\mu(t)t^{d}\sum_{i=1}^{\frac{ ...
- 【bzoj4174】tty的求助 莫比乌斯反演
Description Input 输入仅有一行. 第一行仅有两个正整数N,M 和一个实数 x. Output 输出共1行,由亍结果过大,所以请输出上式对998244353 取模的结果. Sampl ...
- 【二分+容斥+莫比乌斯反演】BZOJ2440 完全平方数
Description 求第k个没有完全平方因子的数,k<=1e9. Solution 这其实就是要求第k个µ[i](莫比乌斯函数)不为0的数. 然而k太大数组开不下来是吧,于是这么处理. 二分 ...
- 页面性能优化-原生JS实现图片懒加载
在项目开发中,我们往往会遇到一个页面需要加载很多图片的情况.我们可以一次性加载全部的图片,但是考虑到用户有可能只浏览部分图片.所以我们需要对图片加载进行优化,只加载浏览器窗口内的图片,当用户滚动时,再 ...
- 安卓开发笔记(二十八):仿写IOS switch选择器控件实现,checkbox
我们先来看看效果: 这里我们主要使用了github上的一个开源项目,配置起来比较方便,下面解释一下该如何使用:首先是:Gradle文件当中进行配置: dependencies { implementa ...
- 解决tomcat部署项目中碰到的几个问题
在tomcat上部署项目并进行测试,经常会碰到各种问题.在不同的操作系统上部署,对问题的解决也会有一些差异. 1 发现问题 1.1 项目部署 先将项目达成war包,放到tomcat的webapps目录 ...
- Vue.js-10:第十章 - 组件间的数据通信
一.前言 在上一章的学习中,我们继续学习了 Vue 中组件的相关知识,了解了在 Vue 中如何使用组件的 data.prop 选项.在之前的学习中有提到过,组件是 Vue 中的一个非常重要的概念,我们 ...
- sau交流学习社区—vue总结:使用vue的computed属性实现监控变量变化,使用vue的watch属性监控变量变化从而实现其他业务
有时候遇到这么个需求,输入框为空的时候,请求一遍接口,如果输入框不为空的时候,需要点击搜索按钮请求接口. 同步sau交流学习社区:https://www.mwcxs.top/page/464.html ...
- Java进阶篇之十五 ----- JDK1.8的Lambda、Stream和日期的使用详解(很详细)
前言 本篇主要讲述是Java中JDK1.8的一些新语法特性使用,主要是Lambda.Stream和LocalDate日期的一些使用讲解. Lambda Lambda介绍 Lambda 表达式(lamb ...
- CopyOnWriteArrayList你都不知道,怎么拿offer?
前言 只有光头才能变强 前一阵子写过一篇COW(Copy On Write)文章,结果阅读量很低啊...COW奶牛!Copy On Write机制了解一下 可能大家对这个技术比较陌生吧,但这项技术是挺 ...