Java IO 的自述
我是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 你用的可能就更少了 !
(完)
你看到的只是冰山一角, 更多精彩文章,参见《码农翻身2016文章精华》
有心得想和大家分享? 欢迎投稿 ! 我的联系方式:微信:liuxinlehan QQ: 3340792577
公众号:码农翻身
“码农翻身”公众号由工作15年的前IBM架构师创建,分享编程和职场的经验教训。
编辑推荐
掘金是一个高质量的技术社区,从移动开发到架构设计,编程语言到开源类库,让你不错过互联网开发的每一个技术干货。长按图片二维码识别或者各大应用市场搜索「掘金」,技术干货尽在掌握中。