Java學習77:OutputStream

在這裏插入圖片描述

和InputStream相反,OutputStream是Java標準庫提供的最基本的輸出流。

和InputStream類似,OutputStream也是抽象類,它是所有輸出流的超類。這個抽象類定義的一個最重要的方法就是void write(int b),簽名如下:

public abstract void write(int b) throws IOException;

這個方法會寫入一個字節到輸出流。要注意的是,雖然傳入的是int參數,但只會寫入一個字節,即只寫入int最低8位表示字節的部分(相當於b & 0xff)。

和InputStream類似,OutputStream也提供了close()方法關閉輸出流,以便釋放系統資源。要特別注意:OutputStream還提供了一個flush()方法,它的目的是將緩衝區的內容真正輸出到目的地。

爲什麼要有flush()?因爲向磁盤、網絡寫入數據的時候,出於效率的考慮,操作系統並不是輸出一個字節就立刻寫入到文件或者發送到網絡,而是把輸出的字節先放到內存的一個緩衝區裏(本質上就是一個byte[]數組),等到緩衝區寫滿了,再一次性寫入文件或者網絡。對於很多IO設備來說,一次寫一個字節和一次寫1000個字節,花費的時間幾乎是完全一樣的,所以OutputStream有個flush()方法,能強制把緩衝區內容輸出。

通常情況下,我們不需要調用這個flush()方法,因爲緩衝區寫滿了OutputStream會自動調用它,並且,在調用close()方法關閉OutputStream之前,也會自動調用flush()方法。

但是,在某些情況下,我們必須手動調用flush()方法。舉個栗子:

小明正在開發一款在線聊天軟件,當用戶輸入一句話後,就通過OutputStream的write()方法寫入網絡流。小明測試的時候發現,發送方輸入後,接收方根本收不到任何信息,怎麼肥四?

原因就在於寫入網絡流是先寫入內存緩衝區,等緩衝區滿了纔會一次性發送到網絡。如果緩衝區大小是4K,則發送方要敲幾千個字符後,操作系統纔會把緩衝區的內容發送出去,這個時候,接收方會一次性收到大量消息。

解決辦法就是每輸入一句話後,立刻調用flush(),不管當前緩衝區是否已滿,強迫操作系統把緩衝區的內容立刻發送出去。

實際上,InputStream也有緩衝區。例如,從FileInputStream讀取一個字節時,操作系統往往會一次性讀取若干字節到緩衝區,並維護一個指針指向未讀的緩衝區。然後,每次我們調用int read()讀取下一個字節時,可以直接返回緩衝區的下一個字節,避免每次讀一個字節都導致IO操作。當緩衝區全部讀完後繼續調用read(),則會觸發操作系統的下一次讀取並再次填滿緩衝區。

FileOutputStream
我們以FileOutputStream爲例,演示如何將若干個字節寫入文件流:

public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write(72); // H
    output.write(101); // e
    output.write(108); // l
    output.write(108); // l
    output.write(111); // o
    output.close();
}

每次寫入一個字節非常麻煩,更常見的方法是一次性寫入若干字節。這時,可以用OutputStream提供的重載方法void write(byte[])來實現:

public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write("Hello".getBytes("UTF-8")); // Hello
    output.close();
}

和InputStream一樣,上述代碼沒有考慮到在發生異常的情況下如何正確地關閉資源。寫入過程也會經常發生IO錯誤,例如,磁盤已滿,無權限寫入等等。我們需要用try(resource)來保證OutputStream在無論是否發生IO錯誤的時候都能夠正確地關閉:

public void writeFile() throws IOException {
    try (OutputStream output = new FileOutputStream("out/readme.txt")) {
        output.write("Hello".getBytes("UTF-8")); // Hello
    } // 編譯器在此自動爲我們寫入finally並調用close()
}

阻塞
和InputStream一樣,OutputStream的write()方法也是阻塞的。

OutputStream實現類
用FileOutputStream可以從文件獲取輸出流,這是OutputStream常用的一個實現類。此外,ByteArrayOutputStream可以在內存中模擬一個OutputStream:

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class Demo03 {
    public static void main(String[] args) throws IOException {
        byte[] data;
        try(ByteArrayOutputStream output=new ByteArrayOutputStream()){
           output.write("Hello".getBytes("Utf-8"));
           output.write("world".getBytes("utf-8"));
           data=output.toByteArray();

        }
        System.out.println(new String(data,"utf-8"));
    }
}

ByteArrayOutputStream實際上是把一個byte[]數組在內存中變成一個OutputStream,雖然實際應用不多,但測試的時候,可以用它來構造一個OutputStream。

小結
Java標準庫的java.io.OutputStream定義了所有輸出流的超類:

  • FileOutputStream實現了文件流輸出;

  • ByteArrayOutputStream在內存中模擬一個字節流輸出。

某些情況下需要手動調用OutputStream的flush()方法來強制輸出緩衝區。

總是使用try(resource)來保證OutputStream正確關閉。

謝謝觀看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章