资讯专栏INFORMATION COLUMN

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

fuyi501 / 724人阅读

摘要:从应用流向目的地称为输出流,从目的地流向应用称为输入流。装饰器模式装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。以及家属对于装饰器模式的体现,也以此类推。

流概述

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中还有许多其它设计模式的体现,比如说监听者模式等等。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65366.html

相关文章

  • Java IO

    摘要:分类一按操作方式类结构字节流和字符流字节流以字节为单位,每次次读入或读出是位数据。该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区字符数组,通过内部的指针来操作字符数组中的数据。 分类一:按操作方式(类结构) 字节流和字符流: 字节流:以字节为单位,每次次读入或读出是8位数据。可以读任何类型数据。 字符流:以字符为单位,每次次读入或读出是16位数据。其只能读取字符类...

    Salamander 评论0 收藏0
  • 第十五章 输入输出系统

    摘要:在包下主要包括输入输出两种流,每种输入输出流又可分为字节流和字符流两大类。输入输出是从程序运行所在的内存的角度而言的。的输入流主要由和作为基类,而输出流主要由和作为基类。 本章主要参考和摘自疯狂java讲义上面的(java编程思想的后面看过后有新的内容再补充进去吧)。  输入输出是所有程序都必需的部分————使用输入机制允许程序读取外部数据(包括磁盘、光盘等存储设备上的数据和用户输入的...

    hankkin 评论0 收藏0
  • 乐字节Java之file、IO基础知识和操作步骤

    摘要:流分类由此可见,流很庞大从不同角度进行分类数据分类按处理数据单位分为字节流和字符流。处理数据是音频视频文本等一切为字节流,仅能处理文本的为字符流。功能分类节点流和处理流。从向一个特定的设备磁盘网络等读写数据的流称为节点流,也常被称为低级流。 嗨喽,小乐又来了,今天要给大家送上的技术文章是Java重点知识-IO流。 先来看看IO流的思维导图吧。showImg(https://segmen...

    pkhope 评论0 收藏0
  • 设计模式在jdk中的应用

    摘要:本文只是寻找设计模式在中的应用。来补全这一块工厂模式中的应用包线程池解释和代码线程池中有线程创建工厂。状态模式中的应用解释和代码根据一个指针的状态而改变自己的行为适配器模式中的应用解释和代码将一个类的接口转换成客户希望的另外一个接口。 前言 最近重学设计模式,而且还有很多源码要看。所以就想一举两得。从源码中寻找设计模式。顺便还可以看看源码。。。本文只是寻找设计模式在java中的应用。优...

    dingding199389 评论0 收藏0
  • BIO、伪异步 IO、AIO和NIO

    摘要:采用通信模型的服务端通常由一个独立的线程负责监听客户端的连接它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理处理完成之后通过输出流返回应答给客户端线程销毁这就是典型的一请求一应答通信模型该模型最大的问题就是缺乏弹性伸缩能力 BIO 采用 BIO 通信模型的服务端, 通常由一个独立的 Acceptor 线程负责监听客户端的连接, 它接收到客户端连接请求之后为每个客户端创...

    ideaa 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<