java零基础入门-高级特性篇(十三) IO 流 3
本章介绍另一个流派-字符流。其实如果上一章字节流已经掌握的话,字符流学起来会更加简单。先来看看字符流的结构,他与字节流的结构稍有不同。
字节流中主要的类有4个,而字符流中有6个,简单介绍一下这6个类。输入流中FileReader是核心,虽然是核心类,但是其实它本身没有什么功能,类中只有构造器,它继承InputSteamReader类,主要功能来自于继承的InputSteamReader类。而BufferedReader是将FileReader包装后,具有缓冲功能的字符输入流,文件较大请使用带缓冲功能的流。
FileReader
FileReader的例子和上一章字节流的写法十分相似,也是首先根据文件路径创建File对象,然后设置一个字符数组,最后循环输出读入的字符。用法难度不大,这里主要来看看FileReader里面到底在干什么。
FileReader里面只有3个构造器,而构造器里面创建了字节流然后作为参数传给了父类的构造器。为什么字符流里面用的是字节流?往下看,父类InputSteamReader的构造器里面有一个关键的类-StreamDecoder,这个类的作用就是对字节流进行编码和解码。这样一来,就能弄清楚FileReader和InputSteamReader到底是做什么用的了。
FileReader的作用,其实是表示“我是字符流家族的”,然而它背地里偷偷跟字节流家族搭上线,它的存在只是保证了字符流家族的完整性。
InputSteamReader的作用,帮助FileReader在背地里将字节流进行编码转换,让字符流看上去是在直接处理字符,所以他还有个名字叫做“转换流”。
虽然字节流和字符流都可以处理中文,但是在处理需要编码的文字的时候还是建议使用字符流,这样会减少发生错误的几率。
FileWriter
字符输出流看上去要简单很多,首先根据路径参数创建一个文件,然后将内容写入到创建的文件中。但是这里要注意一个地方,就是FileWriter的flush方法。这个方法比较重要,因为如果在最后不调用这个方法的话,文字是不会被写人到文件的。原因就是FileWriter有缓冲区,flush方法就是将缓冲区中的内容清空,把文字写入到文件。这里肯定有同学会问了,这个FileWriter名字上没有Buffered这个标识啊,为啥会有缓冲区?这里要说明一个概念,FileWriter是有默认的缓冲区大小的,而带有Buffered的缓冲输出流的缓冲区是可以配置大小的。
字符缓冲流
和字节流一样,更高级的缓冲流只需要对字符流进行一层包装即可,然后流就具有缓冲功能了,并且是可以设置缓冲区大小的缓冲功能。上节说过,流是需要关闭的,在关闭流的过程中,会自动检查缓冲区的内容是否被清空,如果没有清空缓冲区就关闭流,程序会自动调用flush方法清空缓冲区。所以,一般只要记得关闭流就可以了,不需要特意去清空缓冲区。
装饰模式
前面讲过工厂模式,这里再来讲一个装饰模式,因为缓冲流这种设计,就是最好的装饰模式实现。装饰模式是啥?我们先来吃把鸡就应该懂了。最近吃鸡游戏很火,要想吃鸡,你需要装备性能好,配置高的武器才行,比如我比较喜欢用的是M416这把武器。这把武器的配件非常多,虽然搜起来比较困难,但是一旦全部搜齐了,交战起来火力那是可以秀敌人一脸的。
没有配件的M416这把枪就是下面的样子。如果没有配件,用起来是非常困难的,首先没有瞄准镜,瞄准敌人很困难,其次没有握把和枪头,开火后难以控制准星,所以我们需要尽快搜到配件安装到枪上,才能提高我们的作战能力。
来看看如何设计代码,首先游戏里面肯定不止这一种枪,除了步枪还有手枪,狙击枪等等,所以老规矩,将枪作为父类提出来,作为抽象类,可以实现枪的公用方法,也可以只要抽象方法,不同的枪都来继承它就行了。M416继承了枪这个类以后,只需要实现枪这个类定义的抽象方法即可。
这里只实现了一个M416,当然各位也可以实现其他的枪,比如SCAR,98k等等。只需要继承Gun这个父类即可。但是这里要说明的不是实现各种枪,而是要实现枪的配件,下面看看一把有配件的M416长什么样。
现在要开始设计带有配件的M416的代码了。那么问题就来了,配件不是固定的,我们可能刚落地就搜到所有配件,也有可能直到游戏结束都只能搜到一个配件,所以我们设计的带有配件的M416不是固定的,也就是说我们有很多种可能会出现的M416形态。
看到这么多种可能,有的同学可能掐指一算,嗯~1+2+3=6个类,撸起袖子开始顺着写6个类。我想说,其实我这里只是简化了配件数量,其实M416的配件还有弹夹和枪托,并且每个部位的配件可能不止一种,比如弹夹还可以分为扩容弹夹和快速弹夹,真要一个个写下来,可能几十个类都写不完。那么该怎么办?让他们自由组合就可以了,好了,装饰模式登场了。
装饰模式可以动态的给一个对象加上更多的责任(功能),在不需要创造更多的子类的情况下,将对象的功能进行扩展。来看看如何使用装饰模式解决上面这个问题。
首先创建一个抽象装饰类,这个类的作用就是动态的传入对象,这个对象只要是Gun或者Gun的子类都可以,简单的说,只要传入一把枪就行,然后用具体装饰类对这个抽象装饰类进行“装饰”,加上各种功能即可。
抽象装饰类,用于维护一个对象,这个对象可以是任何一个M416,没有配件或者有配件都可以,然后在具体的装饰类中,可以继续对这个对象进行功能的添加,这样就可以在M416的基础上,配置各种配件,而不需要为特定的形态创建新的类。
首先创建一个无配件的M416,再创建3个带配件的M416,最后创建一个有所有配件的M416。这里最重要的就是好好理解抽象装饰类的作用和具体装饰类的作用,正因为有了这两个重要的部件,才能灵活的对各种功能进行组装。
理解了上面这个例子,再来对比一下这个例子和IO流中的设计,来看看IO流中如何使用装饰模式。
左边的结构就是上例的结构,各位应该已经明白了。再来看右图,有没有发现结构几乎一样?其实不止结构一样,连功能都几乎没有区别,前面章节说过InputStream是所有字节输入流的抽象基类,其实他扮演的角色和Gun一样,他就是为了构建各种字节输入流而存在。FileInputStream就像没有配件的M416一样,他只有最简单的功能,如果需要额外的功能,需要创建抽象装饰类,然后创建带有新功能的具体装饰类。来看看代码上的对比。
缓冲流的用法就是将普通流作为参数传递给缓冲流构造器,这与M416加配件的过程一样。都是把没有附加功能的类,包装成带有各种新功能的“高级类”,而在IO流中,当然就是“高级流”了。流的家族很大,这里只介绍了最常用的缓冲流BufferedInputStream,其实还有很多其他带有功能的高级流,比如上图中LineNumberInputStream也是一种带有功能的高级流,它的功能是获取当前输入流的行数和设置行数,这些都是具体的装饰类。
以上就是字节输入流的装饰模式展示,其他字节输出流,字符的输入输出流也都是装饰模式,结构上大同小异。这也是为什么IO流家族体系庞大的原因之一。