🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
实际工作中,使用Java IO的API来操作文件的机会并不多,更多的时候只需要配置文件(xml , properties)放到指定的位置,剩下的工作就交给框架来处理,框架会把配置数据变成Java对象来调用,不用操心IO细节。 其实IO就是输入输出的意思,文件只是一个IO的例子而已。从网络读写数据也是IO,IO是对他们的抽象。 我们把的自己当做是程序,从外面读数据的时候就用InputStream,向外边写数据的时候就用OutputStream Stream 这个词也很有意思,想象一下,你从文件 / 网络读取数据, 这些数据像河流一样 “流” 向你, 是不是很形象? 由于是“流”, 你读到了第 100 个字节, 然后想退回到第 10 个字节重新读, 那我是不允许的, 河流是不允许倒退的。 但是计算机中的一切都是二进制字节,但是如果直接使用InputStream或者OutputStream来读文本内容,会有些麻烦,还得翻译成字符,所以Java提供了Reader/Writer接口,专门用于橱窗字符流。 ## 阻塞 IO API的最大问题在于阻塞,比如读取文件中的一行数据 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180823091704.png?picgo) 当一个线程在执行这段代码的时候,遇到了readLine()方法的时候,需要等待数据从硬盘进入内存。 这个线程就会被阻塞。 人们想把它改为非阻塞,也就是调用了readLine()之后,立刻就烦这,线程就可以干其他事去了。 但是线程不就是为了读数据吗,如果变成了非阻塞,调用了readLine()之后,线程可以执行后面的代码,那这个线程岂不是还得做个轮询操作,看看数据是否已读好了。 要是线程打开了成百上千个文件,使用阻塞的方式,只能按照文件1、文件2这样的顺序去读取,太慢了,如果是非阻塞二方式可以同时发起成百上千个读操作,然后在循环中检查, 看谁的数据处理好了,就读谁的。 这种情况常见于Socket编程,一个Socket来了,就创建一个新的线程或者从线程池分配一个线程去处理这个连接,要是并发连接太多了,就只能多创建线程。 但是线程太多也有问题,每个线程都会占用内存空间,数量多了系统受不了,线程之间的切换是非常要命。 所以非阻塞的方式比较好,让一个线程管理成百上千个socket连接,就像管理多个文件一样,不用做线程的切换。 因为在某一个时刻,不是每个socket都有数据读写,很多时候都是空闲的,完全可以用轮询的方式来查看哪些socket可以读写,进行操作。 不过因为Stream,Reader用得太多了,不能轻举妄动,可以设计一套新的API,叫Java NIO 也就是non-blocking IO 里面有几个关键概念: * Channel:可以和原来的Steam类比,就是通过Channel读写数据,是非阻塞的,一个socket也是channel的一种。 * Buffer:通过Channel读写的数据都在Buffer,有于Buffer不是流,读到Buffer的尾部还可以从头读。 * Selector:和Channel配合使用,Channel可以把自己注册到Selector里面,告诉Selector要监听XXX事件,这是一个线程可以管理多个 Channel的关键 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180823093309.png?picgo) 在一个无限循环中让一个线程处理多个连接 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180823093347.png?picgo)