1. 程式人生 > >javaIO(1):OutputStream和FileOutputStream原始碼分析及“裝飾者模式”在IO中的應用

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體系就很容易理解啦。