为什么要有NIO

我是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 你用的可能就更少了 !


猜你喜欢

转载自blog.csdn.net/u010325193/article/details/80310924