看到一篇文章介绍Java IO的设计,感受讲的太好了,忍不住想要转过来,原网址看起来会更舒服java
Java IO-InputStream家族 -装饰者模式 陈霖 育树霖疯 2017-05-16 在学习java.io.*包的时候,Input-Stream那一群类很让人反感,子类繁多就不用说,使用起来很是奇怪。咱们想以缓存的方式从文件中读取字节流。总要先建立一个FileInput-Stream,而后把这个File-InputStream放入Buffered-InputStream构造函数中去建立BufferedInputStre-am。完成这些工做后才能开始读取文件。数组
(以缓存方式从文件中读取数据输入流的JAVA代码)缓存
为何咱们不能直接建立以缓存方式从文件中读取数据的输入流类呢?今天我带着这样的问题走进InputStream的家,但愿他可以给我答案。函数
我:老人家,我想问问,为何咱们想以带缓存的方式从文件中读取字节流须要建立FileInputStream和BufferedInput-Stream两个类,这么麻烦的实现方式您是怎么想到的?这种设计是否是太缺心眼了?学习
InputStream:年轻人,这要从好久好久之前提及。那时候Java刚刚被创造出来,我也幸运的被创造出来。那时候我尚未任何子孙,孤家寡人一个,无所事事。一天,有一个叫小霍的年轻人找到了我。他说他要让我飞黄腾达,子孙满堂。线程
我:这么神?那您讲讲您和小霍之间的故事吧。设计
时光倒流回20年前,小霍初见InputStream。对象
小霍:InputStream你好,我是JAVA帝国计划生育委员会的工做人员,今天我带着任务而来,组织决定让您儿孙满堂。继承
InputStream:此话当真?
小霍:我小霍是谁啊,必须比啊。而了实现这个目标,我已经从计划生育委员会给你争取了好几个生育名额。
InputStream:我知道从那争取到名额很不容易,那你说说,组织准备让我生几个?
小霍:你是IO的输入类,负责读取数据(字节流)。数据就是你的包裹,你通常从哪些渠道得到包裹?
InputStream:文件,字节数组,StringBuffer,其它线程,对了还有已经被序列化的对象。
小霍:那你先根据数据来源的渠道生5个孩子,老大叫FileInputStream处理文件,老二叫ByteArrayInputStream处理字节数组,老三叫StringBufferInputStream处理StringBuffer,老四叫PipedInput-Stream处理线程间的输入流,老五叫ObjectInputStream处理被序列化的对象。
InputStream:万一有一个包裹里面有多个或者多种数据输入流呢。
小霍:那就再生一个SequenceInputStre-am处理一个包裹里有多种数据来源的业务。还有其它问题吗?没问题我就回单位了。帝国刚创建,咱们计划生育委员会掌管着全国的生育名额,我还忙着呢。你抓紧时间生孩子,有问题再找我。
InputStream:好咧,我这就关灯造人。
交流完毕后,小霍走了,我也抓紧时间把我6个孩子生了出来,为国家作贡献。In-putStream的6个孩子齐心合力,处理了JAVA早期不少的输入业务。可是他们也面临了新的问题。没过多久。年轻的计生委员小霍再次找到了InputStream。
小霍:你那6孩子都是能人啊,可是如今客户抱怨他们的工做还不够到位。
InputStream:我那6个孩子个个工做勤勤恳恳,怎么还不到位?
小霍:客户嘛,都比较挑剔。他们抱怨大家读取数据太慢了,尤为是你的老大FileInputStream每次读数据都慢死了。好多客户等待都超过了几十秒了,还没把数据等回来。
InputStream:几十秒很慢吗?
小霍:咱们计算机都是以纳秒计时的,所谓世间一秒钟,机器已千年。那些客户头发都等白了。
InputStream: 读数据慢能怪我吗?这不是硬盘慢形成的吗?
小霍:是,是硬盘形成的,咱们想一个办法让用户减小访问硬盘的次数。好比建一个buffer怎么样?用户须要的数据先让他们在buffer里面找,能找到就直接从buffer里返回,实在找不到再去硬盘里找。Buffer在内存里,内存可比硬盘快10万倍呢(内存在随机访问的速度上是硬盘的10万倍以上)。
InputStream:这办法好。客户抱怨的其余问题呢?
小霍:客户想要的数据类型都是int, long, String这样的JAVA基本类型,而你提供给他们的都是byte类型,还须要客户本身进行类型转换。客户以为麻烦。还有一个问题,stream里面读出来的数据就不能从新放回stream,客户想要一个功能,能把读出来的数据再推回stream里面。
InputStream:看来我得再生3个孩子: 拥有缓存的BufferedInputStream,把byte转换成JAVA基本类型的DataInput-Stream和回写数据到stream的Push-backInput-Stream。
小霍:老伙计,你糊涂了,不止3个。你想一想,你已经有6个孩子。他们掌握着6种数据来源。若是每个数据来源都分别实现这3个新功能。你至少得生18个孩子吧。还有更大的问题,万一客户既想有数据回写,又想要输出int,long,String这样的数据,还有要缓存。或者有的客户只须要这3个新功能的其中2个呢?那么一个数据源须要7个新类,6兄弟一共是42个。
InputStream:看来咱们得想象另外的办法。这样会形成类爆炸。再说让我一下生那么多孩子,我也煎熬啊。
小霍:你玩过俄罗斯套娃没?一个实心的娃娃被各类各样娃娃外壳套着。一个实心娃娃先套一个学生的外壳,那么他就是学生了,若是我再在外面套一个运动员的壳,那么他就成了有运动员身份的学生。咱们模仿这种形式,好比最里面的实心娃娃是处理文件读取的FileInputStream,外面套一个BufferedInputStream的壳,那么这个套娃就是带buffer的FileInput-Stream。若是再套一个DataInputStre-am,那么套娃就成了能输出int这样java 基本类型而且带buffer的FileInputStre-am。搭配由客户去决定,咱们只须要提供套娃壳(新的3个功能类)和最里面的实心娃InputStream(InputStream的6个孩子)。客户在搭配套娃的时候必须有一个实心娃娃,不然就没有数据来源。
(套娃图)
InputStream:这很巧妙,那如何实现这样一种设计呢?
小霍:有2个关键点:
1,既然套娃中必定有实心娃娃,那么套娃的壳的类必须包含一个实心娃。好比BufferedInputStream里面要包含一个InputStream,咱们经过BufferedInput-Stream的构造函数把这个实心娃娃Input-Stream放进去,固然DataInput-Stream和PushbackInputStream也同样。
2,BufferedInputStream+实心娃娃InputStream组成的新套娃又能够看成DataInputStream的实心娃娃,那么咱们让这些套娃的外壳BufferedInputStre-am,DataInputStream,BufferedInputStream都继承自InputStream类,这样才能实现新组成的套娃又能够被另外的套娃壳嵌套。这3个套娃壳有着共同的特色都是装饰实心娃娃,咱们再在他们上层在抽象一个FilterInputStream,让FilterInput-Stream继承自InputStream,让Filter-InputStream里面包含一个实心娃娃In-putStream。之后全部的装饰类都从FilterInputStream继承。
InputStream:这样我也省事了,只须要再多生一个FilterInputStream,剩下的BufferedInputStream,DataInputStream,PushbackInputStream这样的装饰类都让FilterInputStream去生了。
小霍:对,加上FilterInputStream,你就有7个孩子了,跟葫芦娃同样。前面6个哥哥都是和数据源有关,7弟就是用来装饰6个哥哥。把数据源的InputStream类和装饰的InputStream整合在了一块儿。
InputStream:并且对于BufferedInput-Stream,DataInputStream,PushbackInputStream来讲,我仍是爷爷,想着他们叫我爷爷的样子,我内心就美滋滋的。
小霍:美的你,Java中不管多少次继承都是子类父类关系,没有爷爷这么一说。我把家谱给你。你儿子太多,我就画Byte-ArrayInputStream,FileInputStream 和FilterInputStream的简化版。
(装饰者模式类图)
小霍:这样的设计既避免了类爆炸,又可让用户本身去搭配核心类和装饰类。并且也知足了一个重要的设计原则:开闭原则。这是指导思想。所谓开闭原则就是要对扩展开放,对修改关闭。咱们的目标是容许类很容易的进行扩展,在不修改代码的状况下就能够搭配新的行为。至于缺点嘛,在实例化的时候用户不只仅实例化核心类,还要把核心类包进装饰者中。对于初次接触IO类库的人,没法轻易理解。
InputStream:这是一个好的设计模式,只是须要一些学习成本。之后要有人不理解这设计模式,我就把我和你之间的故事给他讲一遍。
小霍:甚好。
时光回到如今.
InputStream:故事讲完了,这下你明白了吗?
我:原来是这样啊,要是按照我起初的想法,有一个专门的类来处理个人InputFile-Stream+BufferedInputStream,那Input-Stream你早就由于类太多引发爆炸了。小霍是个厉害的人物啊。
InputStream:是啊,年轻人就的多学习。小霍确实是个了不得的人物。
结束语:有人问过关于文章里说起的人物真实存在吗?其实大多数都是我杜撰的。而本文中的小霍确有其人。亚瑟.范.霍夫(Arthurvan Hoff)早期杰出的Java工程师,大多数的Java.io类都出自他手。后来也担任过Flipboard,Dell公司CTO.谢谢他把这么精彩的设计带到人间。文中说起的全部类InputFileStream,Buffered-InputStream等均可以在java.io.*中找到,有兴趣的能够去读读源码,jdk的源码就是最规范的java规范,最详细的文档。