javaIO(1):OutputStream和FileOutputStream源码分析及“装饰者模式”在IO中的应用

前言

一,IO体系

从现在起,我们将基于JDK1.8详细介绍java.io包中的关于输入输出有关的类。了解过这个包的都知道,里面的类继承关系错综复杂,光是弄清楚这些类的关系就够喝一壶的了。说实话,我也没有什么好的方法来一下子就能弄清这些类,但是如果你了解“装饰者模式”的话,了解了其中一类流体系的话,就能类比记忆其他体系。其中的类大致分成5类体系,其中流体系有4类:

1,File类。包里面有一个单独的File类,这个类是文件和目录路径名的抽象表示形式。这个类里面包含了很多与文件或路径有关的方法,如:创建和删除文件或者路径,获取文件或路径的属性,判断文件或路径是否具有一些性质等。尽管输入输出设备有很多,但是操作最多的还是硬盘,而数据在硬盘上的表现形式就是文件,即File。

2,OutputStream及其子类:输出字节流。这个体系将数据按照字节格式写入到输出设备(硬盘或屏幕等)。

3,InputStream及其子类:输入字节流。这个体系读取不同输入设备(键盘,硬盘等)的字节数据源。

4,Writer及其子类:输出字符流。这个体系将数据按照字符格式写入到输出设备(硬盘或屏幕等)。

5,Reader及其子类:输入字符流。这个体系将数据按照字符格式写入到输出设备(硬盘或屏幕等)。

二,IO中的“装饰者模式”

本文先讲OutputStream字节输出流体系,下面是这个体系的继承关系图。其中1,2,3,4是OutputStream的直接子类,他们分别实现了父类的write()等方法,而且写的形式和目的地各不相同,用来完成不同的任务。还有一个直接子类FilterOutputStream,它及其子类5,6,7就构成了“装饰者模式”,比如子类5:BufferedOutputStream,它可以接受1,2,3,4等直接子类,完成特定任务的同时,使用了缓冲区,提高了效率。

这里写图片描述

这里写图片描述

正文

File类的使用很简单,不再赘述。本文说一下OutputStream字节输出流及其子类的部分源码,以及“装饰者模式”的应用。

一,OutputStream源码

package java.io;

// OutputStream是所有字节输出流的超类,并实现2个接口,这两个接口种分别有一个方法close()和flush()
public abstract class OutputStream implements Closeable, Flushable {

    /* 
    将指定字节写入此文件输出流,子类需实现该方法。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。
    */
    public abstract void write(int b) throws IOException;

    // 将 b.length 个字节从指定 byte 数组写入此文件输出流中。 
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    // 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
    public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);  // 去调用wirte(int b)方法一个一个写
        }
    }

    /* 刷新此输出流并强制写出所有缓冲的输出字节。flush 的常规协定是:如果此输出流的实现已经缓冲了以前写入的任何字节,则调用此方法指示应将这些字节立即写入它们预期的目标。*/
    public void flush() throws IOException {
    }

    /*
     关闭此输出流并释放与此流有关的所有系统资源。close 的常规协定是:该方法将关闭输出流。关闭的流不能执行输出操作,也不能重新打开。
     */
    public void close() throws IOException {
    }

}

二,FileOutputStream源码

OutputStream的直接子类,完成特定功能,即以字节的形式将数据写入到文件中。较常用。

package java.io;

import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;


/**
 * 文件输出流用于将字节数据写入到文件中。
 *
 * 该流可以用作写图像数据。要写字符,则考虑FileWriter
 *
 * 是OutputStream的直接子类
 */
public class FileOutputStream extends OutputStream
{
    /**
     * 打开文件句柄
     */
    private final FileDescriptor fd;

    /**
     * 是否在文件末尾追加
     */
    private final boolean append;

    /**
     * 引用对象
     */
    private FileChannel channel;

    /**
     * 文件路径
     */
    private final String path;

    private final Object closeLock = new Object();
    private volatile boolean closed = false;

    // 构造方法1,传入表示文件路径的字符串,将字符串初始化为File对象,传给构造方法4
    public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

    // 构造方法2,传入表示文件路径的字符串和是否追加标识,将字符串初始化为File对象,传给构造方法4
    public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }

    // 构造方法3,传入File对象,调用构造方法4
    public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

    // 构造方法4,前3个构造方法都调用这个构造方法
    public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        this.fd = new FileDescriptor();
        fd.attach(this);
        this.append = append;
        this.path = name;

        open(name, append); // open调用native方法,打开指定的文件
    }

    // 创建一个向指定文件描述符处写入数据的输出文件流
    public FileOutputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkWrite(fdObj);
        }
        this.fd = fdObj;
        this.append = false;
        this.path = null;

        fd.attach(this);
    }

    // 实现父类的write方法
    public void write(int b) throws IOException {
        write(b, append);  // 调用native方法,一次写一个字节。
    }

    // 重写了父类的write(byte[])方法
    public void write(byte b[]) throws IOException {
        writeBytes(b, 0, b.length, append); // 调用native方法,写字节数组
    }

    // 重写了父类的write方法
    public void write(byte b[], int off, int len) throws IOException {
        writeBytes(b, off, len, append); // 调用native方法,写字节数组一部分
    }

    // 重写了父类的close()方法,同样调用native方法实现资源关闭
    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }

        if (channel != null) {
            channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

    // flush继承自父类,父类的flush方法啥都不做,自然FileOutputStream的flush方法也是啥也不做。

三,ByteArrayOutputStream和ObjectOutputStream等

2个直接子类和其他直接子类同样实现了特定的write()方式,代码不再贴出来了。

四,FilterOutputStream,抽象装饰者类

该类继承自OutputStream,但是它上面都不干,只持有父类的对象,并调用父类的同名方法。


package java.io;


public class FilterOutputStream extends OutputStream {

    protected OutputStream out;  // 持有父类的对象

    public FilterOutputStream(OutputStream out) {
        this.out = out;  // 构造方法传入父类对象
    }

    public void write(int b) throws IOException {
        out.write(b);  // 调用父类的同名方法
    }

    public void write(byte b[]) throws IOException {
        write(b, 0, b.length); 
    }

    public void write(byte b[], int off, int len) throws IOException {
        if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
            throw new IndexOutOfBoundsException();

        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);  
        }
    }

    public void flush() throws IOException {
        out.flush();   // 调用父类的同名方法
    }


    @SuppressWarnings("try")
    public void close() throws IOException {
        try (OutputStream ostream = out) {
            flush();
        }
    }
}

五,BufferedOutputStream,具体装饰者类

该类是继承自FilterOutputStream,自然也就继承了父类的OutputStream类型成员变量out。也就可以为OutputStream的直接子类提供特定的“装饰”,即缓冲区。

package java.io;

public class BufferedOutputStream extends FilterOutputStream {

    protected byte buf[]; // 内部缓冲区

    protected int count;  // 缓冲区中的有效字节数

    public BufferedOutputStream(OutputStream out) {
        this(out, 8192);  // 构造具有OutputStream对象和固定大小缓冲区的对象
    }

    public BufferedOutputStream(OutputStream out, int size) {
        super(out);   // 调用父类的构造器传入OutputStream对象
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size]; // 初始化自定义大小的缓冲区
    }

    private void flushBuffer() throws IOException { // 刷新缓冲区方法
        if (count > 0) {
            out.write(buf, 0, count);
            count = 0;
        }
    }

    /*
    BufferedOutputStream的write()方法底层虽然也是调用了OutputStream的write()方法,但是这个write()方法在OutputStream的write()方法基础上添加了缓存的功能,即对原write()方法进行了“装饰”,从而提高了效率。
    */
    public synchronized void write(int b) throws IOException {
        if (count >= buf.length) {
            flushBuffer();  // 缓冲区满了就刷新
        }
        buf[count++] = (byte)b; // 缓存字节数据
    }

    // 同理,新write()方法对老write()方法进行了“装饰”,即功能的增强。
    public synchronized void write(byte b[], int off, int len) throws IOException {
        if (len >= buf.length) {
            /* If the request length exceeds the size of the output buffer,
               flush the output buffer and then write the data directly.
               In this way buffered streams will cascade harmlessly. */
            flushBuffer();
            out.write(b, off, len);
            return;
        }
        if (len > buf.length - count) {
            flushBuffer();
        }
        System.arraycopy(b, off, buf, count, len);
        count += len;
    }

    public synchronized void flush() throws IOException {
        flushBuffer();
        out.flush();
    }
}

六,DataOutputStream和PrintStream等具体装饰者类。

同BufferedOutputStream对OutputStream进行“缓冲区装饰”,还有其他的很多具体装饰者类对OutputStream进行各种各样的“装饰”。如:DataOutputStream以适当方式将基本 Java 数据类型写入输出流中,PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。

代码不再贴出来了。

总结

现在,我们总结了OutputStream体系的所有类,发现如下几条规律:

1,OutputStream作为超类,定义了该体系最基本的方法。

2,OutputStream的直接子类(除了FilterOutputStream),实现了各种情况下所需的write()方式。

3,FilterOutputStream类及其子类是对2中各直接子类的“装饰”,目的是提供额外的功能。

经过以上总结,再类比到InputStream,Reader,Writer体系就很容易理解啦。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值