千家信息网

如何从Java IO源码看装饰者模式的用法

发表于:2024-11-19 作者:千家信息网编辑
千家信息网最后更新 2024年11月19日,这篇文章主要讲解了"如何从Java IO源码看装饰者模式的用法",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何从Java IO源码看装饰者模式的用法
千家信息网最后更新 2024年11月19日如何从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继续包装BufferedInputStreamDataInputStream从基础输入流中读取原始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中对应的就是汉字。这里只是解释原理,不涉及如何正确读取原始数据的问题。

在这个例子当中,DataInputStreamBufferedInputStream用于增强功能,也就是装饰者角色,而FileInputStream是数据源,是被装饰者,他们都有着共同的方法,并且装饰者要持有一个被装饰者或者其他装饰者的引用(这里通过构造方法注入),所以他们应当有共同的父类。这些装饰器可以自由组合,以实现不同的功能。所以,如果不这样做的话,还有更好的实现方式来达到同样的效果吗?完整的继承关系如下图:

抽象类图

  1. 首先有个被装饰对象

  2. 然后有一堆的装饰器用于增强不同的功能

  3. 装饰器与被装饰对象具有相同的行为(超类中的抽象方法)

  4. 装饰器应当持有被装饰者的引用或者其他装饰者(持有超类的引用,多态的体现)

  5. 组合这些装饰器和被装饰对象(构造方法注入)

感谢各位的阅读,以上就是"如何从Java IO源码看装饰者模式的用法"的内容了,经过本文的学习后,相信大家对如何从Java IO源码看装饰者模式的用法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0