Java IO-InputStream家族 -装饰者模式
最近看到一篇文章,初步介绍java.io.InputStream,写的非常通俗易懂,在这里我完全粘贴下来。
来源于 https://mp.weixin.qq.com/s/hDJs6iG_YPww7yeiPxmZLw
在学习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:万一有一个包裹里面有多个或者多种数据输入流呢。
小霍:那就再生一个SequenceInputStream处理一个包裹里有多种数据来源的业务。还有其它问题吗?没问题我就回单位了。帝国刚建立,我们计划生育委员会掌管着全国的生育名额,我还忙着呢。你抓紧时间生孩子,有问题再找我。
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规范,最详细的文档。
下面我们单独看看SequenceInputStream,用来合并流。
public class SequenceInputStream extends InputStream {Enumeration<? extends InputStream> e;InputStream in;/*** Initializes a newly created <code>SequenceInputStream</code>* by remembering the argument, which must* be an <code>Enumeration</code> that produces* objects whose run-time type is <code>InputStream</code>.* The input streams that are produced by* the enumeration will be read, in order,* to provide the bytes to be read from this* <code>SequenceInputStream</code>. After* each input stream from the enumeration* is exhausted, it is closed by calling its* <code>close</code> method.** @param e an enumeration of input streams.* @see java.util.Enumeration*/public SequenceInputStream(Enumeration<? extends InputStream> e) {this.e = e;try {nextStream();} catch (IOException ex) {// This should never happenthrow new Error("panic");}}/*** Initializes a newly* created <code>SequenceInputStream</code>* by remembering the two arguments, which* will be read in order, first <code>s1</code>* and then <code>s2</code>, to provide the* bytes to be read from this <code>SequenceInputStream</code>.** @param s1 the first input stream to read.* @param s2 the second input stream to read.*/public SequenceInputStream(InputStream s1, InputStream s2) {Vector<InputStream> v = new Vector<>(2);v.addElement(s1);v.addElement(s2);e = v.elements();try {nextStream();} catch (IOException ex) {// This should never happenthrow new Error("panic");}}...}
主要的构造方法有两个,我们分别来距离来举例看一下。
例一:
public class SequenceInputStreamTest {public static void main(String[] args) throws Exception {Vector<InputStream> v = new Vector<InputStream>(); v.add(new BufferedInputStream( new FileInputStream("D:/eclipse_4.4/mywork/myselfWork/src/1.txt"))); v.add(new FileInputStream("D:/eclipse_4.4/mywork/myselfWork/src/2.txt")); Enumeration<InputStream> en = v.elements(); SequenceInputStream sis = new SequenceInputStream(en); FileOutputStream fos = new FileOutputStream("D:/eclipse_4.4/mywork/myselfWork/src/3.txt"); byte[] buf = new byte[1024]; int len = 0; while((len=sis.read(buf))!=-1) { fos.write(buf,0,len);} fos.close(); sis.close(); }}
例二:
public class SequenceInputStreamTest {public static void main(String[] args) throws Exception {BufferedInputStream bf = new BufferedInputStream( new FileInputStream("D:/eclipse_4.4/mywork/myselfWork/src/1.txt")); FileInputStream file = new FileInputStream("D:/eclipse_4.4/mywork/myselfWork/src/2.txt"); SequenceInputStream sis = new SequenceInputStream(bf,file); FileOutputStream fos = new FileOutputStream("D:/eclipse_4.4/mywork/myselfWork/src/3.txt"); byte[] buf = new byte[1024]; int len = 0; while((len=sis.read(buf))!=-1) { fos.write(buf,0,len);} fos.close(); file.close(); bf.close();}}
这两个方法,都将1.txt和2.txtx写入了3.tet。
再看看 PushBackInputStream,可以向输入流中添加功能,对数据处理后允许使用unread回推数据。
public class PushBackInputStream {public static void main(String[] args) {byte[] b = {1,2,3};//PushBackInputStream可以向输入流中添加功能,允许使用unread回推数据。PushbackInputStream pbi = new PushbackInputStream (new ByteArrayInputStream(b));int result;try {while ((result = pbi.read()) != -1) {System.out.print("取出:" +result + ";");//读出字节,对字节进行处理,然后回推到流中result = result+1;pbi.unread(result);pbi.read();System.out.println("回推后取出:"+result + "。");}} catch (IOException e) {e.printStackTrace();}try {pbi.close();} catch (IOException e) {e.printStackTrace();}}}
结果:
转载于:https://www.cnblogs.com/caozx/p/9045839.html
Java IO-InputStream家族 -装饰者模式相关推荐
- struts2文件下载出现Can not find a java.io.InputStream with the name的错误
今天在用struts2就行文件下载时出现如下错误: Servlet.service() for servlet default threw exception java.lang.IllegalArg ...
- Java IO: InputStream
转载自 Java IO: InputStream 译文链接 作者: Jakob Jenkov 译者: 李璟(jlee381344197@gmail.com) InputStream类是Java I ...
- Can not find a java.io.InputStream with the name [downloadFile] in the invocation stack.
1.错误描述 八月 14, 2015 4:22:45 下午 com.opensymphony.xwork2.util.logging.jdk.JdkLogger error 严重: Exception ...
- 初学Java常用设计模式之——装饰器模式
声明:转载请附上原文链接 提示:标题序号从8开始,是照应不同设计模式笔记发布的顺序而定的,比如,上一篇文章 初学Java常用设计模式之--桥接模式和组合模式 序号从7开始. 8. 装饰器设计模式(重点 ...
- Java设计模式12:装饰器模式
装饰器模式 装饰器模式又称为包装(Wrapper)模式.装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰器模式的结构 通常给对象添加功能,要么直接修改对象添加相应的功能, ...
- java中什么是装饰者模式? 装饰者模式的使用!!
其实无论是代理模式还是装饰模式.本质上我认为就是对原有对象增强的方式~ 一.对象增强的常用方式 很多时候我们可能对Java提供给我们的对象不满意,不能满足我们的功能.此时我们就想对Java原对象进行增 ...
- java I/O之装饰者模式
装饰者: Decorator模式(别名Wrapper):动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案. 装饰者模式意图: 动态的给一个对象添加额外的职责.Decorato ...
- JAVA设计模式初探之——装饰者模式
2019独角兽企业重金招聘Python工程师标准>>> 这个模式花费了挺长时间,开始有点难理解,其实就是 定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorat ...
- Java常用设计模式之装饰者模式
在我们进行Java开发的时候,很多时候我们可能对Java提供给我们的对象不满意,不能满足我们的功能.此时我们就想对Java原对象进行增强,能够实现我们想要的功能就好. 一般来说,实现对象增强有三种方式 ...
- JAVA设计模式初探之装饰者模式
这个模式花费了挺长时间,开始有点难理解,其实就是 定义:动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活. 设计初衷:通常可以 ...
最新文章
- wallpaper怎么改后缀_腾讯微信视频号怎么引流?腾讯视频号引流有哪些方法?
- 李笑来 css,李笑来都想投资千万美金的ACSS通证即将强势登陆奇点交易所
- Java多线程之线程虚假唤醒
- .net面向对象学习笔记
- 数据治理的陷阱与解决方案
- 磁盘结构非常详细的介绍
- 自定义控件之圆形的image
- SQL语法 之 基本查询
- 扫描网络计算机mac地址,局域网MAC地址查询扫描器
- 前言:智能车制作的那些事
- wampserver显示红色、橙色的解决方案
- java flex 聊天_【转帖】实现了视频私聊功能
- 未来测试软件推荐,未来函数检测工具优化版(支持所有股软未来检测)
- java的 反射机制
- 一直在构建工作空间_国土空间规划一周知识整理(2020.11.09-2020.11.15)
- Git关于commit的操作,修改message,合并commit,撤销commit
- 老是抓不住伦敦黄金实时行情,怎么办?
- Android 在线下载更新App 下载完成安装APK(兼容Android7.0)
- 干货 | 分段的人脸检测在移动段的应用
- python自动更新pom文件
热门文章
- springboot学习笔记2106版
- 阶段3 3.SpringMVC·_06.异常处理及拦截器_6 SpringMVC拦截器之拦截器入门代码
- 阶段3 1.Mybatis_09.Mybatis的多表操作_6 分析mybatis多对多的步骤并搭建环境
- 阶段3 1.Mybatis_09.Mybatis的多表操作_3 完成account的一对一操作-通过写account的子类方式查询...
- 阶段1 语言基础+高级_1-3-Java语言高级_08-JDK8新特性_第1节 常用函数接口_9_常用的函数式接口_Consumer接口...
- 第一回 钟碧诚 被迫上屋顶
- Codeforces 1110D. Jongmah 动态规划
- MapReduce-实践1
- IT部门域事件与业务分析
- Can't add self as subview crash错误