OpenJDK 源碼閱讀之 Java 字節流輸出類的實現

Java 的輸入輸出老是給人一種很混亂的感受,要想把這個問題搞清楚,必須對各類與輸入輸出相關的類之間的關係有所瞭解。只有你瞭解了他們之間的關係,知道設計這個類的目的是什麼,才能更從容的使用他們。


這是這個系列的第二篇,描述字節輸出類的實現,第一篇見:OpenJDK 源碼閱讀之 Java 字節流輸入類的實現


字節流輸出

java_io_write_bytes

圖1 Java 字節輸出類java

  • OutputStream

OutputStream是全部字節輸出類的超類,這是個抽象類,須要實現其中定義的 write 函數,纔能有實用的功能。git

    public abstract void write(int b) throws IOException;

其它方法都是在 write 的基礎上實現的。例如這個多態的 write :github

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]);
    }
}
  • FileOutputStream

FileOutputStream 會將內容輸出到 File 或者 FileDescriptor, 此類是按照字節輸出,若是想按照字符輸出,可使用FileReader 類。數組

構造器中,須要指明輸出的文件:markdown

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();
    }
    this.fd = new FileDescriptor();
    this.append = append;

    fd.incrementAndGetUseCount();
    open(name, append);
}

寫入操做是一個 native 函數,與操做系統相關。app

private native void write(int b, boolean append) throws IOException;

若是對比一下字節輸入類,你會發現輸入和輸出在實現上有很大的類似性,它們是對稱的。函數

  • ByteArrayOutputStream

ByteArrayOutputStream 會將數據寫入字節數組中, 能夠經過 toByteArray,toString 獲得這些數據。post

protected byte buf[];

初始化時,能夠指定這個數組的大小:this

public ByteArrayOutputStream(int size) {
    if (size < 0) {
        throw new IllegalArgumentException("Negative initial size: "
                                           + size);
    }
    buf = new byte[size];
}

寫入時,會寫入這個數組。write 會先保證數組的大小,若是不夠用,還會自動進行擴充。spa

public synchronized void write(int b) {
    ensureCapacity(count + 1);
    buf[count] = (byte) b;
    count += 1;
}
  • FilterOutputStream

全部有過濾功能的類的基類,例如,對輸出流進行轉化,或者添加新的功能。初始化時,須要提供一個底層的流,用於寫入數據,FilterOUtputStream 類的全部方法都是經過調用這個底層流的方法實現的。

初始化時,

protected OutputStream out;
public FilterOutputStream(OutputStream out) {
    this.out = out;
}

寫入時:

public void write(int b) throws IOException {
    out.write(b);
}
  • BufferedOutputStream

BufferedOutputStream 是 FilterOutputStream 的子類,提供緩衝功能,因此,你不用每寫入一個字節都要調用操做系統的write 方法,而是積累到緩衝區,而後一塊兒寫入。

緩衝區就是一個字節數組,在構造器中被初始化。

protected byte buf[];

public BufferedOutputStream(OutputStream out) {
    this(out, 8192);
}

public BufferedOutputStream(OutputStream out, int size) {
    super(out);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}

當調用 write(b) 時,並不真正寫入,而是將要寫入的數據存放在緩衝區內,等緩衝區滿後,一次性寫入數據。

public synchronized void write(int b) throws IOException {
    if (count >= buf.length) {
        flushBuffer();
    }
    buf[count++] = (byte)b;
}
  • DataOutputStream

DataOutputStream 能夠按 Java 的基本類型寫入數據。寫入的原理是,將基本類型數據中的字節分離出來,而後將這些字節寫入。例如:

public final void writeBoolean(boolean v) throws IOException {
    out.write(v ? 1 : 0);
    incCount(1);
}

boolean 類型就是按照 0/1 的方式寫入的。

public final void writeShort(int v) throws IOException {
    out.write((v >>> 8) & 0xFF);
    out.write((v >>> 0) & 0xFF);
    incCount(2);
}

short 是兩個字節,須要將其中的兩個字節分離出來,分別寫入,incCount 加了2. writeChar 同理,由於它也是寫入兩個字節。

浮點數比較特殊,無法直接分離出各個字節,要調用 Float 的一個靜態方法,把浮點數轉化成四個字節,再經過 writeInt 寫入。floatToInitBits 會調用一個 native 方法, 按照 IEEE 754 標準,完成其主要功能。

    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }
  • PipedOutputStream

管道輸出流能夠與一個管道輸入流相關聯,關聯後,共用一個緩衝區,輸出流寫入數據,輸入流讀取數據,兩者應該處於不一樣線程,不然可能出現死鎖。

原理上一篇文章在介紹 PipedInputStream 時,已經闡述。

另外,我以爲在這裏,有必要說一下那幾個用於壓縮和解壓縮的類,實現就不說了,就講下他們的功能與關係。

JAVA IO 壓縮與解壓縮

  • InflaterInputStream: 用於解壓 deflate 格式的壓縮數據,底層流爲壓縮後的數據,read 返回解壓後的數據。
  • InflaterOutputStream: 用於解壓 deflate 格式的壓縮數據,底層流爲壓縮後的數據,write 寫入解壓後的數據。
  • DeflaterInputStream: 用於壓縮成 deflate 格式的數據,底層流爲未壓縮數據,read 返回壓縮後的數據。
  • DeflaterOutputStream: 用於壓縮成 deflate 格式的數據,底層流爲未壓縮數據,write 寫入壓縮後的數據。

  • GZIPInputStream: 用於解壓 GZip 格式的壓縮數據,底層流爲壓縮後的數據,read 返回解壓後的數據。它是 InflaterInputStream 的子類。

  • GZIPOutputStream: 用於壓縮成 Gzip格式的數據,底層流爲未壓縮數據,write 寫入壓縮後的數據。是 DeflaterOutputStream 的子類(注意不是InflaterOutputStream) 。

不得不說,這個API設計的真是太反直覺了。GZIP 格式的解壓和壓縮一個是 GZIPInputStream,一個是 GZIPOutputStream。而 deflate 格式的解壓和壓縮,一個是 InflaterInputStream/InflaterOutputStream,另外一個是 DeflaterInputStream/DeflaterOutputStream。當同時須要對 gzip 和 deflate 壓縮和解壓縮時,就感受,真是反直覺。

相關文章
相關標籤/搜索