头条面试官:NIO 是不是就是I/O多路复用?我:不是
似乎从互联网起源,BIO、NIO 的话题就从未间断,无论是平时还是面试。那么他们到底是什么?希望你看完这个文章彻底理解这些概念,同时这边文章也使用 Java 代码实现一个 I/O 多路复用的实例,最后到 I/O 原理。
IO 是什么?
首先要了解什么是 I/O,一次网络请求、一次磁盘读取都是I/O,所以可以泛泛的理解,数据需要通过媒介进行读取和写入都是IO。
下面是一次网络内容读取的 I/O 示意图,数据从外设(网卡)到内核空间,到用户空间(JVM)最后到应用程序的一个过程,那么阻塞和非阻塞指的是哪里呢?
我们换一个调用链路图说明一下阻塞的位置,第一张图解释的是 BIO,也就是同步阻塞 IO,Java 最早期的版本的 IO 就是这样实现的。当程序调用到读取IO的时候,同步阻塞住程序,直到数据从网卡写入内核空间,再写入用户空间才返回数据,程序才可以继续运行。等到 Java 1.4 版本以后就支持了 NIO,其实 NIO 解决的地方是从网卡到内核空间部分的阻塞,也就是说应用程序发送一个读取 IO 的请求,如果数据还没有从网卡写入内核空间,直接返回未就绪,这样就做到了不需要程序死等到结果。等到写入内核空间以后,程序继续读取数据,这时候才会阻塞程序,如下图。
IO 多路复用
基本的 IO 了解了,那我们开始进入 IO 多路复用的理解。假设我们要实现一个在线聊天系统,一共有 100 人同时聊天,我们先不管“路”多不多,需要同时支持 100 人我们需要怎么做?
我们需要为每一个请求用户创建一个新的线程,才能同时支持多个用户同时使用,然鹅,随着用户越来越多,需要创建的线程也越来越多,频繁的上下文切换和线程的创建和销毁,对于系统性能影响非常大,具体的链路可以通过下面的图理解下。
那么是不是可以去掉多线程环节,使用一个线程处理 N 个 IO 请求,没有上下文切换和线程的创建和销毁,程序是不是反而更快呢?这就是 IO 多路复用的思路。
首先我们从字面上理解一下,多路复用的英文是 multiplexing,中文翻译为“复用”,其实简单的理解就是使用单个线程通过记录跟踪每一个流(I/O)的状态来同时管理多个I/O,以此来减少线程的创建和切换的开销,进而提高服务器的吞吐能力。那么这么解释下来就更容易理解了,看图一目了然。
既然使用单线程解决多个 IO 的切换问题,那么必须不能阻塞,如果阻塞了还是需要串行的读取 IO,阻塞程序的,切换就没有意义了,所以 NIO 并完全等于IO多路复用,而是解决多路复用的必要条件“非阻塞“,这也回答了标题的问题。
自己实现 IO 多路复用
上文了解了什么是 IO 多路复用自己也就可以实现了,下面就是我用 Java 写的 IO 多路复用的代码,代码只是表达意思,有问题可以互相讨论。
public void start() throws IOException {ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(6789));serverSocketChannel.configureBlocking(false);while (true) {SocketChannel channel = serverSocketChannel.accept();register(channel);Set<SocketChannel> selectionKeys = selectedKeys();if (selectionKeys.size() != 0) {for (SocketChannel socketChannel : selectionKeys) {handle(socketChannel);}}}
}private void register(SocketChannel channel) {if (channel != null) {publicKeys.add(channel);}
}private Set<SocketChannel> publicKeys = new HashSet<>();private Set<SocketChannel> selectedKeys() {Set<SocketChannel> publicSelectedKeys = new HashSet<>();for (SocketChannel fd : publicKeys) {if ((fd.validOps() & OP_READ) != 0) {publicSelectedKeys.add(fd);}}return publicSelectedKeys;
}private void handle(SocketChannel socketChannel) throws IOException {ByteBuffer byteBuffer = ByteBuffer.allocate(512);StringBuilder sb = new StringBuilder();while (socketChannel.read(byteBuffer) > 0) {byteBuffer.flip();sb.append(StandardCharsets.UTF_8.decode(byteBuffer));}if (sb.length() > 0) {System.out.println("服务端收到消息:" + sb.toString());}
}
逻辑相对还是比较简单,直接介绍几个关键的信息。
定义
register
方法,当有新的连接进来直接添加到publicKeys
set 集合里面,这样就有了一个地方可以存放所有的 socket 连接。定义
selectedKeys
方法,用于获取所有ready
状态的 socket,原理是使用(fd.validOps() & OP_READ) != 0
遍历所有的在publicKeys
里面的 socket判断是否可以读取了,然后添加到selectedKeys
结果集里面,因为fd.validOps()
这个判断是非阻塞的,所以可以继续运营程序。外层
while (true)
循环获取可以读取的socket
从而实现 IO 多路复用逻辑。
是不是逻辑很清晰?然后写一个超级简单的 Client
就可以测试验证了。
public void start() throws IOException {SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(6789));socketChannel.configureBlocking(false);Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String request = scanner.nextLine();if (request != null && request.length() > 0) {socketChannel.write(StandardCharsets.UTF_8.encode(request));}}
}
系统实现
好了,那么问题来了,自己实现的 IO 多路复用不就可以了吗?为啥网上不停的吹嘘 select、poll、epoll 啥的?到这里我们一起看下 select
的源码你就懂了。
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
我们简单的对这些参数讲解下
nfds: 监控的文件描述符集里最大文件描述符加1,简单理解为需要循环遍历的文件数
readfds/writefds/exceptfds:读/写/异常事件的监听描述符集合。
timeout:阻塞的超时时间
如果你想更深入研究下源码,可以直接到 https://www.kernel.org/ 下载源码,找到 fs/select.c
文件即可。
话题回到最初,通过上面的参数我们了解到,系统层面的 select
的实现其实是更底层的实现了刚才我们写的循环遍历查找思路,只是直接在系统层面性能更高。
那么 poll、epoll 又是什么呢?poll 也是操作系统提供的系统调用函数,它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。epoll 则是最后的升级版本,程序调用 epoll 的时候不需要全量传递了,支持变化传递,同时不需要循环遍历,而是通过异步 IO 事件唤醒程序。
所以到这里你明白了,其实无论是系统或者是直接自己写代码都是在解决单线程复用的问题,也就是我们文章说的 IO 多路复用。
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢
头条面试官:NIO 是不是就是I/O多路复用?我:不是相关推荐
- 靠一个HashMap的讲解打动了头条面试官,我的秘诀是
最近收集了一份github标星81.6k的Java面试突击手册,文末查看 关注 转发+转发+转发 私信回复关键词 [学习]即可获取~ 预备知识 位运算知识 位运算操作是由处理器支持的底层操作,底层硬件 ...
- 头条面试官:在浏览器地址栏输入一个URL后回车,背后会进行哪些技术步骤?
本题为头条校招测开经典面试题,如果你去看过头条面经合集,会发现3次面试里至少出现有1次,也是非常基础的一个知识点. 好了,不说废话. 先上答案,一共6个步骤: DNS解析 TCP连接 发送http请求 ...
- 头条面试官:5 亿整数的大文件,如何排序 ?
点击上方蓝色"终端研发部",选择"设为星标" 学最好的别人,做最好的我们 作者:foreach_break 来源:https://blog.csdn.net/g ...
- 3 连接sybase_今日头条面试官:给我说说数据库连接池的原理?
作者:敦格 来源:https://urlify.cn/ABJbum 这次我们采取技术演进的方式来谈谈数据库连接池的技术出现过程及其原理,以及当下最流行的开源数据库连接池jar包. # 早期我们怎么进行 ...
- java 中向文本写入和读取hashmap_就靠这一篇HashMap的讲解,我与头条面试官聊了一个小时。...
预备知识 位运算知识(更多资料私信"学习"免费获取) 位运算操作是由处理器支持的底层操作,底层硬件只支持01这样的数字,因此位运算运行速度很快.尽管现代计算机处理器拥有了更长的指令 ...
- 今日头条面试官竟然问我new一个对象背后发生了什么?这太难了...
来源:https://url.cn/5V55xBu 一. 前言 Java在new一个对象的时候,会先查看对象所属的类有没有被加载到内存,如果没有的话,就会先通过类的全限定名来加载. 加载并初始化类完 ...
- “作为字节跳动面试官,有些话我不得不说!”
转载自字节跳动 由于肺炎疫情的影响,不少同学年初跳槽的计划被搁置.虽然计划被打乱,但是这也给我们留出时间更好的准备面试.随着企业复工时间的延长,不少公司裁员.降薪甚至倒闭,之后只会更难. 看了不少「面 ...
- 作为字节跳动面试官,有些话我不得不说!
点击上方"开发者技术前线",选择"星标" 每天 13.21 在看 | 真爱 转载自[字节跳动] 今天看到了一篇关于如何准备面试的文章,从字节跳动出来拿了15个o ...
- java 今日头条面试_【面试分享】今日头条Java面试题,复习资料完整版PDF下载
2021年,字节的技术岗依旧是最香的,而且随着字节的规模不断扩大,机会也越来越多.马上迎来金三银四,很多小伙伴都在撸题备战中. 2021年,字节的技术岗依旧是最香的,而且随着字节的规模不断扩大,机会也 ...
最新文章
- 废芯片做出来的艺术品!值得一看!
- JavaScript之jQuery够用即可(查找筛选器、属性操作、jQuery文档处理)
- (二)surging 微服务框架使用系列之surging 的准备工作consul安装(转载 https://www.cnblogs.com/alangur/p/8377977.html)...
- linux jzmq编译,Linux下ps aux解释
- C#监控设备(PLC)状态仿真模拟器
- 金蝶商贸版密码服务器没显示,金蝶KIS商贸版无损解密用户密码
- 刘宇凡:我是一只特立独行的猪
- hp linux 禁用u盘启动不了,u盘启动禁用旧版启动模式并启用UEFI
- linux看视频装哪个软件,Ubuntu安装视频播放软件 SMPlayer 14.9.0.7042
- 2016-2017-2 《Java程序设计》课程学生博客和代码托管链接
- 企业与个人必备安全测试工具
- 剑指 Offer 05. 替换空格(完整代码)
- 磁盘管理之 raid 文件系统 分区
- Ribbon与Hystrix
- java录入学生信息_java实现学生成绩录入系统
- Python开发技术—网络爬虫
- 信奥一本通-动态规划-例9.2-数字金字塔-方法四-逆推法代码实现
- 第二季度UWA STAR —只有不拒绝问题,才能进一步解决问题
- [CF796E Round#408 Div.2]Exam Cheating——[计数DP]
- 图解项目绩效评价的13个常用方法大全
热门文章
- 英国工程巨头遭勒索攻击:运营临时中断 至少损失4亿元
- 计算机php外文翻译,php外文翻译.doc
- HDU1756(判断一个点是否在多边形内)
- [USACO08JAN]Cell Phone Network G 树形dp
- 2020牛客多校第3场:[Points Construction Problem + 思维题+构造]
- python将注释写入xml_python 注释xml的元素
- 【图论专题】拓扑排序
- 解题报告:NOIP2013 车站分级(拓扑序递推求解差分约束、建图优化O(n+m)) 超详细讲解
- [P1434 [SHOI2002]滑雪](DFS,记忆化搜索)
- c语言的特点能够编制出复杂的功能程序,以下不是C语言的特点的是()