和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正確關閉。
謝謝觀看