如何从Java IO源码看装饰者模式的用法
这篇文章主要讲解了"如何从Java IO源码看装饰者模式的用法",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何从Java IO源码看装饰者模式的用法"吧!
前言
设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。
我们在做业务开发的时候,要对业务进行抽象,抽象就是在理解了业务之后再给出一个定义,一个概念。所以概念是从实际场景反推出来的,而不是先有概念。设计模式既然是对问题的总结,自然符合先有场景再有概念。
要理解设计模式就要到具体的场景中去,事实证明,网上流传的诸多书籍或文章举出的例子,都很勉强。
众所周期,Java IO体系使用了装饰者模式,如果不理解这个模式,就很难熟练的使用io相关的API,不如直接到最经典的例子中去,直接看Java io源码设计,既能理解模式的使用方式,又能熟悉Java IO的用法和体系!
JavaIO源码示例
public static void main(String[] args) throws IOException { File file = new File("file.txt");//文件内容为:abcdefgh InputStream in = new BufferedInputStream( new FileInputStream(file), 512); int firstByte = in.read(); System.out.println(firstByte); int secondByte = in.read(); System.out.println(secondByte); } //输出结果:97,98
这段代码的意思是:使用FileInputStream
从文件中读取数据,使用BufferedInputStream
包装数据,并且分配一个512字节大小的缓冲区。使用 in.read()
读取一个字节的数据。当读取第一个字节的时候,因为缓冲区没有内容,所以会调用FileInputStream
从文件中读取512个字节放入内存缓冲区,当读取第二个字节的时候,因为缓存区已经有数据,所以直接返回。如果不使用BufferedInputStream
,则每次都要到文件中读取一个字节,效率低,这就是使用BufferedInputStream
做缓冲的意义。
这里为什么输出97?文件中使用utf-8编码,使用不定长的1-4个字节来表示一个字符,对于ascii里面的字符只使用一个字节,所以文件中第一个字节即字符a的utf-8的编码,也等于ascii编码,换算成十进制即97。
下面是BufferedInputStream
的构造方法:
public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size];}
下面是BufferedInputStream
的read方法:这里判断pos与count的比较,count是缓冲区中的有效字节数,pos即当前读取位置,构造方法只是初始化一个字节数组,里面还没有有效数据,所以第一次读取时count为0,pos为0,这时候调用fill方法填充缓存区数据。
public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff;}
下面是BufferedInputStream
的fill方法,删减一部分不必要的内容,这里的buffer
就是初始化的缓冲区,其调用了getInIfOpen方法将数据写入缓冲区。
private void fill() throws IOException { byte[] buffer = getBufIfOpen(); count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos;}
getInIfOpen方法使用in
这个对象,这个in
即构造方法传入的FileInputStream
,所以就相当于调用FileInputStream
读取512字节的数据写入到BufferedInputStream
的缓冲区。
private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input;}
可以看到BufferedInputStream
起到的是一个提供缓冲区的增强功能。那么还有没有别的增强功能?
public static void main(String[] args) throws IOException { File file = new File("file.txt");//文件内容:abcde DataInputStream in = new DataInputStream(new BufferedInputStream( new FileInputStream(file), 512)); char c = in.readChar(); System.out.println(c); }//输出结果:慢
这里使用DataInputStream
继续包装BufferedInputStream
,DataInputStream
从基础输入流中读取原始Java数据类型,比如这里使用readChar方法读取第一个字符,但是为什么会输出慢
这个字?查看源码:
public final char readChar() throws IOException { int ch2 = in.read(); //97 int ch3 = in.read();//98 if ((ch2 | ch3) < 0) throw new EOFException(); return (char)((ch2 << 8) + (ch3 << 0)); }
因为Java中的char类型使用2个字节来存储,所以这里读取连续的两个字节来当做一个字符。由于文件中存储的是字母,前面已经解释了,ch2的值应该是97,ch3的值是98,执行(ch2 << 8) + (ch3 << 0)的结果是十进制的24930
,而24930
在utf8中对应的就是汉字慢
。这里只是解释原理,不涉及如何正确读取原始数据的问题。
在这个例子当中,DataInputStream
和BufferedInputStream
用于增强功能,也就是装饰者角色,而FileInputStream
是数据源,是被装饰者,他们都有着共同的方法,并且装饰者要持有一个被装饰者或者其他装饰者的引用(这里通过构造方法注入),所以他们应当有共同的父类。这些装饰器可以自由组合,以实现不同的功能。所以,如果不这样做的话,还有更好的实现方式来达到同样的效果吗?完整的继承关系如下图:
抽象类图
首先有个被装饰对象
然后有一堆的装饰器用于增强不同的功能
装饰器与被装饰对象具有相同的行为(超类中的抽象方法)
装饰器应当持有被装饰者的引用或者其他装饰者(持有超类的引用,多态的体现)
组合这些装饰器和被装饰对象(构造方法注入)
感谢各位的阅读,以上就是"如何从Java IO源码看装饰者模式的用法"的内容了,经过本文的学习后,相信大家对如何从Java IO源码看装饰者模式的用法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!