Java IO : 流,以及装饰器模式在其上的运用


流概述

Java中,流是一种有序的字节序列,可以有任意的长度。从应用流向目的地称为输出流,从目的地流向应用称为输入流。

Java的流族谱

Java的java.io包中囊括了整个流的家族,输出流和输入流的谱系如下所示:

InputStream和OutputStream

InputStream和OutputStream分别是输入输出流的顶级抽象父类,只定义了一些抽象方法供子类实现。

在输出流OutputStream中,如果你需要向一个输出流写入数据,可以调用void write(int b)方法,这个方法会将b的低八位写入流中,高24位将会被自动忽略。如果想要批量写入数据呢,那么可以调用void write(byte[] b) 方法将一个字节数组的内容全部写入流中,同时还有void write(byte[] b, int off, int len)可以让你指定从哪里写入多少数据。
如果你希望你向流中写入的数据能够尽快地输送到目的地,比如说文件,那么可以在写入数据后,调用flush()方法将当前输出流刷到操作系统层面的缓冲区中。不过需要注意的是,此方法并不保证数据立马就能刷到实际的物理目的地(比如说存储)。
使用完流后应该调用其close()方法将流关闭,流关闭时,将会先flush,后关闭。

在输入流InputStream中,可以通过int read() 方法来从流中读取一个字节,批量读取字节可以通过int read(byte[] b)或者int read(byte[] b, int off, int len)来实现,这两个方法的返回值为实际读取到的字节数。如果需要重复读取流中某段数据,可以在读取之前先使用void mark(int readlimit)方法在当前位置做一个记号,之后通过void reset()方法返回到之前做标记的位置,不过在做这些标记操作之前,需要先通过boolean markSupported()方法来确定该流是否支持标记。如果对某些可预知的数据不感兴趣,可以使用long skip(long n)来调过一些流中的一些数据。

使用完流,无论是输入还是输出流,都要调用其close()方法对其进行关闭。

装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种设计模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

以InputStream为例,它是一个抽象类:

public abstract class InputStream implements Closeable {
    ...
    ...
}

并定义有抽象方法

public abstract int read() throws IOException;

该抽象方法由具体的子类去实现,通过InputStream的族谱图可以看到,直接继承了InputStream,并且提供某一特定功能的子类有:

  • ByteArrayInputStream

  • FileInputStream

  • ObjectInputStream

  • PipedInputStream

  • SequenceInputStream

  • StringBufferInputStream

这些子类都具有特定的功能,比如说,FileInputStream代表一个文件输入流并提供读取文件内容的功能,ObjectInputStream提供了对象反序列化的功能。

InputStream这个抽象类有一个子类与上述其它子类非常不同,这个子类就是FilterInputStream,可参见上图中的InputStream族谱图。

翻开FilterInputStream的代码,我们可以看到,它内部又维护了一个InputStream的成员对象,并且它的所有方法,都是调用这个成员对象的同名方法。
换句话说,FilterInputStream它什么事都不做。就是把调用委托给内部的InputStream成员对象。如下所示:

public class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    
    public int read() throws IOException {
        return in.read();
    }
    
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    
    public int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }
    
    public long skip(long n) throws IOException {
        return in.skip(n);
    }
    
    public int available() throws IOException {
        return in.available();
    }
    
    public void close() throws IOException {
        in.close();
    }
    
    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }

    public synchronized void reset() throws IOException {
        in.reset();
    }
    
    public boolean markSupported() {
        return in.markSupported();
    }

FilterInputStream的又有其子类,分别是:

  • BufferedInputStream

  • DataInputStream

  • LineNumberInputStream

  • PushbackInputStream

虽然从上面代码看FilterInputStream并没有做什么有卵用的事,但是它的子类可不同了,以BufferedInputStream为例,这个类提供了提前读取数据的功能,也就是缓冲的功能。可以看看它的read方法:

    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

可以看到,当pos>=count时,意即需要提前缓冲一些数据的时候到了,那么就会调用fill()将缓冲区加满,以便后续读取。由于本文只讨论io流的装饰器模式,所以关于具体实现细节将不会展开讨论,比如本文不会讨论fill()方法是如何实现的,在这里可以先将它当做一个黑盒子。

从这里可以开始感受到,BufferedInputStream就是一个装饰者,它能为一个原本没有缓冲功能的InputStream添加上缓冲的功能。

比如我们常用的FileInputStream,它并没有缓冲功能,我们每次调用read,都会向操作系统发起调用索要数据。假如我们通过BufferedInputStream来装饰它,那么每次调用read,会预先向操作系统多拿一些数据,这样就不知不觉中提高了程序的性能。如以下代码所示:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("/home/user/abc.txt")));

同理,对于其它的FilterInputStream的子类,其作用也是一样的,那就是装饰一个InputStream,为它添加它原本不具有的功能。OutputStream以及家属对于装饰器模式的体现,也以此类推。

JDK中的io流的设计是设计模式中装饰器模式的一个经典示范,如果细心发现,JDK中还有许多其它设计模式的体现,比如说监听者模式等等。


已标记关键词 清除标记
相关推荐
Java IO学习总结 Java操作有关的类或接口: Java类图结构: 的概念和作用 是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为的本质是数据传输,根据数据传输特性将抽象为各种类,方便更直观的进行数据操作。 IO的分类 根据处理数据类型的不同分为:字符和字节 根据数据向不同分为:输入和输出 字符和字节 字符的由来: 因为数据编码的不同,而有了对字符进行高效操作的对象。本质其实就是基于字节读取时,去查了指定的码表。 字节和字符的区别: 读写单位不同:字节以字节(8bit)为单位,字符以字符为单位,根据码表映射字符,一次可能读多个字节。 处理对象不同:字节能处理所有类型的数据(如图片、avi等),而字符只能处理字符类型的数据。 结论:只要是处理纯文本数据,就优先考虑使用字符。 除此之外都使用字节。 输入和输出 对输入只能进行读操作,对输出只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的Java IO对象 1.输入字节InputStreamIO 中输入字节的继承图可见上图,可以看出: InputStream 是所有的输入字节的父类,它是一个抽象类。 ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。 ObjectInputStream 和所有FilterInputStream 的子类都是装饰装饰模式的主角)。 2.输出字节OutputStream IO 中输出字节的继承图可见上图,可以看出: OutputStream 是所有的输出字节的父类,它是一个抽象类。 ByteArrayOutputStream、FileOutputStream 是两种基本的介质,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据, ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰。 3.字节的输入与输出的对应 图中蓝色的为主要的对应部分,红色的部分就是不对应部分。紫色的虚线部分代表这些一般要搭配使用。从上面的图中可以看出Java IO 中的字节是极其对称的。“存在及合理”我们看看这些字节中不太对称的几个类吧! LineNumberInputStream 主要完成从中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入了。 PushbackInputStream 的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译的语法、词法分析部分。输出部分的BufferedOutputStream 几乎实现相近的功能。 StringBufferInputStream 已经被Deprecated,本身就不应该出现在InputStream 部分,主要因为String 应该属于字符的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。 SequenceInputStream 可以认为是一个工具类,将两个或者多个输入当成一个输入依次读取。完全可以从IO 包中去除,还完全不影响IO 包的结构,却让其更“纯洁”――纯洁的Decorator 模式。 PrintStream 也可以认为是一个辅助工具。主要可以向其他输出,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它的综合运用的一个工具而已。一样可以踢出IO 包!System.out 和System.out 就是PrintStream 的实例! 4.字符输入Reader 在上面的继承关系图中可以看出: Reader 是所有的输入字符的父类,它是一个抽象类。 CharReader、StringReader 是两种基本的介质,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。 BufferedReader 很明显就是一个装饰,它和其子类负责装饰其它Reader 对象。 FilterReader 是所有自定义具体装饰的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。 InputStreamReader 是一个连接字节和字符的桥梁,它将字节转变为字符。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。 5.字符输出Writer 在上面的关系图中可以看出: Writer 是所有的输出字符的父类,它是一个抽象类。 CharArrayWriter、StringWriter 是两种基本的介质,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据, BufferedWriter 是一个装饰为Writer 提供缓冲功能。 PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。 OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。 6.字符的输入与输出的对应 7.字符与字节转换 转换的特点: 其是字符和字节之间的桥梁 可对读取到的字节数据经过指定编码转换成字符 可对读取到的字符数据经过指定编码转换成字节 何时使用转换? 当字节和字符之间有转换动作时; 操作的数据需要编码或解码时。 具体的对象体现: InputStreamReader:字节到字符的桥梁 OutputStreamWriter:字符到字节的桥梁 这两个对象是字符体系中的成员,它们有转换作用,本身又是字符,所以在构造的时候需要传入字节对象进来。 8.File类 File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。 9.RandomAccessFile类 该对象并不是体系中的一员,其封装了字节,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点: 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw) 注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页