A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© Manito 中级黑马   /  2017-4-27 22:56  /  946 人查看  /  4 人回复  /   2 人收藏 转载请遵从CC协议 禁止商业使用本文

我是Java IO, 你可能听说过我,了解过我,用我的API写过简单的程序, 但估计大部分人也就此打住, 对我的了解并不深入。

这也难怪,毕竟在实际的工作当中,直接使用我的API来操作文件的机会并不多,  更多时候你只需要把配置文件(xml, properties等)放到指定的位置, 剩下的工作就交给框架去处理了,  框架会把这些配置数据变成Java 对象来让你调用, 不用你去操心IO细节。

有些人以为我只是和文件打交道, 为什么不叫做File, 而叫做这么文绉绉的IO呢 ?

其实IO就是输入输出的意思, 文件只是一个IO的例子而已,  从网络读写数据也是IO啊, 极端的情况我对存读写数据也可以是IO啊。  IO是对他们的抽象。

还有人一直分不清什么时候用InputStream, 什么时候用OuputStream, 老是把他俩搞混, 我可以教你一个简单的方法: 把自己当成程序, 当你从外边读数据到自己这里就用InputStream,  向外边写数据就用OutputStream。

Stream这个词也很有意思,想象一下,你从文件/网络读取数据, 这些数据像河流一样“流”向你, 是不是很形象?   由于是“流”, 你读到了第100个字节, 然后想退回到第10个字节重新读, 那我是不允许的, 河流是不允许倒退的。

追根溯源, 计算机中的一切都是二进制的字节, 包括你现在正在看到的文章, 但是你们码农如果直接用InputStream/OutStream来读取这些文本内容, 势必有点麻烦,还得翻译成字符, 所以为了方便你们我就提供了Reader/Writer 接口, 专门用于处理字符流。

啰嗦的这么多, 还没到正题, 我想给大家说的是昨天发生的一件事。

昨天下午,我一边喝咖啡一边津津有味的欣赏我那优雅的API设计,每一次我都忍不住赞叹那漂亮的对称性(Input, Output对称,  byte和char 对称), 还有这装饰模式和适配器模式, 多么优雅,多么漂亮 ,竟然有人说我过度设计, 真是不知好歹。

正在这时候有人敲门了, 来人自称是帝国语言管理部门的,戴着一副深度眼镜, 我们就称呼他“眼镜”吧。

我把他请进来,一起喝点咖啡, 顺便再炫耀下我这优雅的API。

不料眼镜说: “IO先生, 我奉上司命令, 特意前来帮你把API增强一下。”

我说: “现在已经这么优雅漂亮了, 你们还要改? 小心把它改丑了!”

“不是的, 我此次的目的主要是增强API,  你想想,你的接口有什么问题没有?”

我说:“除了有人说我过度设计之外,好像没什么呀”

“你不知道, 问题都反映到上层部门去了, 其中有个重要的问题就是阻塞的问题”

这个我知道, 比如我去读取文件中的一行数据:

当一个线程在执行这段代码, 遇到readLine()方法的时候, 需要等待数据从硬盘进入内存, 这个线程就会被阻塞,   当然我觉得这也没啥, 很正常啊, 大家不都是这么编程的吗?

可是眼镜说: “不, 有很多人反映了这个问题, 大家想把它改成非阻塞的, 也就是说调用了readLine()方法以后,立刻就返回, 线程就可以干别的事情去了。”

“为什么要去干别的事情? 这个线程不就是为了读数据吗?   好,就如你所说, 现在变成了非阻塞的, 调用了readLine()以后, 线程可以执行后面的代码了, 那这个线程岂不还得做个轮询操作,看看数据是不是已经读好了?”

"这样一来,代码多丑陋啊, 不符合我一贯的美学原则, 还不如我原来的阻塞方式呢"    我觉得眼镜的要求不可思议。

眼镜不服气的说: “要是你的线程打开了成百上千个文件呢?  你这种阻塞的方式,只能按照文件1, 文件2, 文件3......   顺序的读取这么多文件,太慢了.  如果是非阻塞方式, 你可以同时发起成百上千个读操作,  然后在那个循环中检查, 看看谁的数据准备好了,就读取谁的, 效率多高啊。”

我立刻反击道: “谁会那么傻, 用一个线程打开这么多文件?  ”

眼镜苦笑着摇了摇头, 似乎没法说服我了。   我觉得可以送客了,继续我的自我欣赏之旅。

可是他突然眼镜一亮: “你听说过服务器端的Socket编程吗?  ”

“我当然知道, 我还知道这个情况: 一个socket连接来了, 就创建一个新的线程或者从线程池分配一个线程去处理这个连接”

“那要是并发连接数太多了怎么办? ”

”那就多创建线程呗。”

” 每个线程都会占用内存空间, 数量多了系统受不了,线程之间的切换也是个要命的开销啊。 ”

眼镜说的有道理, 我无言以对。

眼镜看我沉默了, 接着趁热打铁: ” 所以大家呼唤非阻塞的方式啊,   让一个线程管理成百上千个sockcet连接,就像管理多个文件一样,这样就不用做线程切换了。”

我似乎有点明白了, 正常情况下, 在某一个时刻, 不是每个socket 都有数据读写, 很多时候都是空闲的,所以完全可以用轮询的方式来查看那些socket可以读写, 进行操作就可以了。

我说: ”好吧, 你想怎么改?  ”

"其实我一来就给你说了, 我们是想增强API, 毕竟你的Stream, Reader用的人太多了, 大家基于他们写了大量代码, 所以不能轻举妄动。 你现在的API在java.io 这个包下面,  我们设计一套新的API,叫java nio, 放到java.nio包下面吧 "

NIO ?  是"non-blocking IO"? , 还是 "new IO" ?  唉, 我是懒得管了。

眼镜早有准备, 拿出了一套设计文档, 给我讲了几个概念:

Channel :  可以和原来的Stream类比, 但是有个关键区别, 那就是通过Channel 读写数据,是非阻塞的, 一个socket 也是Channel 的一种。

Buffer :  通过Channel 读写的数据都在Buffer 中, 由于Buffer 不是流, 你读到Buffer 尾部以后还可以从头再读。

Selector :  和Channel配合使用, Channel 可以把自己注册到Selector当中, 告诉Selector 说, 我要监听XXX事件, 这是一个线程管理多个Channel的关键。



我不得不服气,这是一种我之前没有考虑过的思路, 在一个无限的循环中让一个线程处理如此多的连接。



好吧,既然对我原来优雅的API没有影响, 那就把nio加入进来吧。

但是我相信, 相比IO ,  这个NIO 你用的可能就更少了 !
来自宇宙超级黑马专属安卓客户端来自宇宙超级黑马专属安卓客户端

4 个回复

倒序浏览
回复 使用道具 举报
写的很好,还加上了拟人,就是内容不够丰富
来自宇宙超级黑马专属苹果客户端来自宇宙超级黑马专属苹果客户端
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马