OutputStream分析

在Java I/O中,抽象類OutputStream是其他輸出流類(如FileOutputStream)的基礎類,分析一下這個類的源碼很有必要。

概要

這個抽象類實現了兩個接口:Closeable和Flushable。需要注意的是,在這個類的API中寫到這個抽象了實現了三個接口,還包括AutoCloseable,這是因爲Closeable接口繼承了AutoCloseable接口的緣故。類定義如下:
[java] view plaincopy
  1. public abstract class OutputStream implements Closeable, Flushable  

該類使用默認的構造函數,沒有參數。類中定義了1個抽象方法和4個具體方法,其中close()和flush()是對接口中方法的實現(其實是空實現,什麼都沒有)。方法聲明分別如下:
[java] view plaincopy
  1. public abstract void write(int b) throws IOException;  

[java] view plaincopy
  1. public void write(byte b[]) throws IOException  

[java] view plaincopy
  1. public void write(byte b[], int off, int len) throws IOException   

以上三個方法都是用於寫出字節,具體請看下文。還有兩個空實現的方法:
[java] view plaincopy
  1. public void flush() throws IOException {  
  2. }  
[java] view plaincopy
  1. public void close() throws IOException {  
  2. }  


方法分析

下面是對各個方法的分析,先來看write(int b)

write(int b)

這是唯一一個抽象方法,子類中必須提供具體的實現。這個方法寫出一個字節到輸出流中。仔細看一下參數,發現是int類型,也就是有4個字節,那是如何處理的呢?很容易想到,只取低8位,也就意味這int的取值範圍爲0-255.如果提供一個超出這個範圍的參數,將自動把高的24位去掉,具體地說,如果給定一個b超出範圍,則將b除以256取模。例如,傳遞一個256,實際上寫入的是1。將這個值寫入輸出流之後,輸出流的另一端如何解析,取決於另一端。例如,對於控制檯,是將其轉換成ASCII碼輸出。子類必須實現這一方法,例如,FileOutputStream就調用本地方法來實現這個方法。如果寫出過程遇到錯誤,拋出IOException,例如說,這個流已經被關閉,則拋出這個異常。

write(byte b[], int off, int len)

上一個方法一次只寫入一個字節,許多時候是不方便的,這個方法則寫入data字節數組中的len字節到輸出流。參數中data就是待寫入的字節(不一定全部寫入),從第offset位開始寫入,off+len-1是最後一位被寫入的。在OutputStream這個抽象類中,直接循環調用write(int b)方法,如下:
[java] view plaincopy
  1. if (b == null) {  
  2.             throw new NullPointerException();  
  3.         } else if ((off < 0) || (off > b.length) || (len < 0) ||  
  4.                    ((off + len) > b.length) || ((off + len) < 0)) {  
  5.             throw new IndexOutOfBoundsException();  
  6.         } else if (len == 0) {  
  7.             return;  
  8.         }  
  9.         for (int i = 0 ; i < len ; i++) {  
  10.             write(b[off + i]);  
  11.         }  

API鼓勵子類提供更高性能的實現方法。在這個方法中,由於字節本身就是8位,沒有超出範圍的情況,所以直接寫入無需轉換。如果傳遞的data爲空,則拋出NullPointerException異常。如果被寫入的部分超出字節數組範圍,拋出IndexOutofBoundsException異常。具體地說,有以下5中情況,也就是if語句當中的5個表達式。除此之外,如果寫入過程出現錯誤,一樣拋出IOException。

write(byte b[])

這個方法其實是上一個方法的特殊情況,當off=0, len=data.length的時候,就得到了這個方法。具體實現也是通過調用上個方法來實現的,如下:
[java] view plaincopy
  1. public void write(byte b[]) throws IOException {  
  2.     write(b, 0, b.length);  
  3. }  

有一點需要說明,如果一次寫入的字節數組太大,可能就會是性能下降,至於多少合適,得根據具體情況而定。比如寫入網絡的時候就需要小一點了,如128字節。寫入文件的話,稍大一點1024.

flush()

這個方法是對Flushable接口的實現。如果在寫出方法的具體實現中,用到了緩衝機制。則這個方法用於將緩衝區的數據“沖刷”到目的地去。這裏需要特別理解,如果寫出的方法用到了操作系統層的抽象,比如說FileOutputStream,那麼該方法只能保證將緩衝區的數據提交給操作系統,但是不能保證數據被寫入到磁盤文件中去。如果沖刷過程出現I/O 錯誤,拋出IOException。OutputStream類中的方法什麼都不做。當輸出流被關閉或者程序退出的時候,緩衝區的數據互自動被沖刷。對於System.out、System.err這樣的輸出流,當調用println()方法或者遇到換行符‘\n’的時候,緩衝數據自動被沖刷。當然,你可以在PrintStream的構造函數中傳遞參數來設置是否自動沖刷。

close()

這是對Closeable接口中close方法的實現。用於關閉輸出流,釋放相關的系統資源(如文件句柄(file handle)或者網絡端口)。關閉之後,該輸出流不能再被操作或者重新打開,否則拋出異常。當然,你可以不關閉,但是這不是一個好習慣,時常會出現嚴重問題。比如你打開文件,正在操作,那你不關閉,其他線程可能就會一直阻塞下去了。可以如下關閉輸出流:
[java] view plaincopy
  1. try {  
  2.   OutputStream out = new FileOutputStream("numbers.dat");  
  3.   // Write to the stream...  
  4.   out.close( );  
  5. }  
  6. catch (IOException ex) {  
  7.   System.err.println(ex);  
  8. }  

當然,這樣有個問題,如果在寫入的時候出現異常,那麼輸出流的close方法就不會被執行,也就是不會被關閉。換一種方法;
[java] view plaincopy
  1. OutputStream out = null;  
  2. try {  
  3.   out = new FileOutputStream("numbers.dat");  
  4.   // Write to the stream...  
  5. }  
  6. catch (IOException ex) {  
  7.   System.err.println(ex);  
  8. }  
  9. finally {  
  10.   if (out != null) {  
  11.     try {  
  12.       out.close( );  
  13.     }  
  14.     catch (IOException ex) {  
  15.       System.err.println(ex);  
  16.     }  
  17.   }  
  18. }  

注意到,這裏的OutputStream聲明放到了try語句外面,必須這樣才能被finally中的語句訪問。這樣寫雖然安全,但是有點醜陋就是了。如果不處理異常而是拋出,那麼可以這樣:
[java] view plaincopy
  1. OutputStream out == null;  
  2. try {  
  3.   out = new FileOutputStream("numbers.dat");  
  4.   // Write to the stream...  
  5. }  
  6. finally {  
  7.   if (out != null) out.close( );  
  8. }  

一個自定義子類

OutputStream的子類至少要重寫write(int b)方法,當然,有些子類把所有方法都重寫或者重寫一部分。下面這個例子寫入的時候什麼都不敢,只是提供一個實現參考:
完整代碼如下:
[java] view plaincopy
  1. import java.io.*;  
  2. public class NullOutputStream extends OutputStream {  
  3.   private boolean closed = false;  
  4.   public void write(int b) throws IOException {  
  5.     if (closed) throw new IOException("Write to closed stream");  
  6.   }  
  7.   public void write(byte[] data, int offset, int length)  
  8.    throws IOException {  
  9.     if (data == nullthrow new NullPointerException("data is null");  
  10.     if (closed) throw new IOException("Write to closed stream");  
  11.   }  
  12.   public void close( ) {  
  13.     closed = true;  
  14.   }  
  15. }  

這個類重寫了出了flush以外的四個方法,因爲寫入方法什麼都不幹,當然沒必須flush啦。實現很容易懂。不多說明。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章