JavaIO流原理之常用字節流和字符流詳解以及Buffered高效的原理

    Java的流體系十分龐大,我們來看看體系圖:
 
     這麼龐大的體系裏面,常用的就那麼幾個,我們把它們抽取出來,如下圖:
 
    一:字節流
        1:字節輸入流
           字節輸入流的抽象基類是InputStream,常用的子類是 FileInputStream和BufferedInputStream。
           1)FileInputStream
           文件字節輸入流:一切文件在系統中都是以字節的形式保存的,無論你是文檔文件、視頻文件、音頻文件…,需要讀取這些文件都可以用FileInputStream去讀取其保存在存儲介質(磁盤等)上的字節序列。
           FileInputStream在創建時通過把文件名作爲構造參數連接到該文件的字節內容,建立起字節流傳輸通道。
           然後通過 read()、read(byte[])、read(byte[],int begin,int len) 三種方法從字節流中讀取 一個字節、一組字節。
           
           2)BufferedInputStream
           帶緩衝的字節輸入流:上面我們知道文件字節輸入流的讀取時,是直接同字節流中讀取的。由於字節流是與硬件(存儲介質)進行的讀取,所以速度較慢。而CPU需要使用數據時通過read()、read(byte[])讀取數據時就要受到硬件IO的慢速度限制。我們又知道,CPU與內存發生的讀寫速度比硬件IO快10倍不止,所以優化讀寫的思路就有了:在內存中建立緩存區,先把存儲介質中的字節讀取到緩存區中。CPU需要數據時直接從緩衝區讀就行了,緩衝區要足夠大,在被讀完後又觸發fill()函數自動從存儲介質的文件字節內容中讀取字節存儲到緩衝區數組。
           BufferedInputStream 內部有一個緩衝區,默認大小爲8M,每次調用read方法的時候,它首先嚐試從緩衝區裏讀取數據,若讀取失敗(緩衝區無可讀數據),則選擇從物理數據源 (譬如文件)讀取新數據(這裏會嘗試儘可能讀取多的字節)放入到緩衝區中,最後再將緩衝區中的內容返回給用戶.由於從緩衝區裏讀取數據遠比直接從存儲介質讀取速度快,所以BufferedInputStream的效率很高。
複製代碼
public synchronized int read() throws IOException {  
    if (pos >= count) {         // 檢查是否有可讀緩衝數據  
        fill();                 // 沒有緩衝數據可讀,則從物理數據源讀取數據並填充緩衝區  
        if (pos >= count)       // 若物理數據源也沒有多於可讀數據,則返回-1,標示EOF  
        return -1;  
    }  

   // 從緩衝區讀取buffer[pos]並返回(由於這裏讀取的是一個字節,而返回的是整型,所以需要把高位置0。如果是讀取一組字節,則返回讀取長度的字節數組)  
    return getBufIfOpen()[pos++] & 0xff;     
}  
  
複製代碼

 

          2:字節輸出流

            字節輸出流的抽象基類是OutputStream,其具體使用的子類是FileOutputStream和BufferedOutputStream。

            1)FileOutputStream

             文件字節輸出流:作爲文件字節輸入流的逆過程,其實就是在創建時通過文件名創建輸出流連接到要寫入的文件處,然後通過 write(int)/write(byte[]) 方法把輸出內容寫到輸出流中。

            2)BufferedOutputStream

             帶緩衝的字節輸出流:其優化輸出速度的思路也是通過在內存中建立緩衝區,CPU直接把內容寫到內存中的緩衝區,這樣比較快。之後CPU繼續幹自己的事,後臺並行地進行耗時慢速度的真正輸出操作——把緩衝區的數據輸出到輸出流,寫入文件的存儲介質中。

             在創建BufferedOutputStream時,通過一個outputStream參數(在創建outputStream時通過文件名建立起輸出流)把已經建立的輸出流包裝成帶緩衝的輸出流,在內存中創建一個默認大小是8M的緩衝數組;

             在程序運行過程中,通過write(int)/write(byte[])方法向緩衝數組寫入數據,如果某時刻緩衝數組滿了,則自動觸發壓入操作——把數組內容寫到真正的輸出流去,傳輸到文件中。

             如果你想在某些write操作後確保內容能及時輸出而不等到數組滿時自動輸出,則可以調用 flush() 方法強行刷新數組,把緩衝數組的內容全部寫入輸出流。

 

    二:字符流

            字符流是專門用來讀寫文檔文件的高速輸入輸出流。

            爲何要有字符流:上面我們說到了文件字節流是可以讀寫一切文件的,包括文檔文件,那爲何還要多創造一個字符流呢?

                                   首先我們要知道,文檔文件在系統中的呈現原理:文件在系統中是以字節形式存在的,那麼它在系統中如何表示成字符?因爲它從系統中讀取出來呈現時經過了系統的某種編碼格式進行編碼,從而顯示成了字符。比如:我們知道的UTF-8或者漢語系統中的GBK編碼形式,就可以把中文文檔的字節序列讀取出來解碼成中文呈現。

                                   然後我們需要知道:Java程序是運行在Java虛擬機上面的,Java虛擬機也是一個系統,但是它和文件直接保存的所在系統不一樣,有可能雙方對文檔文件的字節序列的解碼格式不一樣。所以,如果直接讀取字節序列,然後讓Java虛擬機來解碼呈現的話有可能因爲編碼格式不一致而導致亂碼顯示。要解決這個問題,就有了字符流。

                                   字符流的原理:它可以在創建時,指定流的編碼形式,使得讀取到的字節序列根據其在系統中保存時採用的編碼格式進行解碼,然後把解析好的字符交給Java虛擬機使用,這樣就避免了文件所在的系統與Java虛擬機解碼不一致導致亂碼。

 

             1:字符輸入流

             字符輸入流的抽象基類是Reader,其常用子類有 InputStreamReader和BufferedReader。

             1)InputSreamReader

             最基本的字符輸入流。

             在創建時,通過包裝一個連接到文檔文件的字節輸入流,並指定編碼格式(不指定則採用默認字符集)對字節輸入流進行解碼(底層是通過創建一個相應編碼格式的流解碼器StreamDecoder實現的,這裏就不展開了)。

複製代碼
InputStreamReader(InputStream in) 
          創建一個使用默認字符集的 InputStreamReader。 
InputStreamReader(InputStream in, Charset cs) 
          創建使用給定字符集的 InputStreamReader。 
InputStreamReader(InputStream in, CharsetDecoder dec) 
          創建使用給定字符集解碼器的 InputStreamReader。 
InputStreamReader(InputStream in, String charsetName) 
          創建使用指定字符集的 InputStreamReader。 
複製代碼

            然後通過

 int read() 
          讀取單個字符。 
 int read(char[] cbuf, int offset, int length) 
          將字符讀入數組中的某一部分。 

             方法讀取字符:因爲在讀取過程中已經經過解碼,所以獲得的結果是字符char而不是字節byte。

 

             2)BufferedReader

             帶緩衝的字符輸入流:原理與BufferedInputStream一樣,都是在內存中維護一個足夠大的緩衝區。每次讀時從緩衝區讀取數據並解碼,緩衝區空了則自動調用fill()填充緩衝區。

             唯一區別在於:BufferedRead除了read()、read(char[])兩個方法外,多了一個  readLine() 方法:讀取一個文本行,通過下列字符之一認爲某行已終止:換行 (‘\n’)、回車 (‘\r’) 或回車後直接跟着換行。

 

             2:字符輸出流

             字符輸出流的抽象基類是 Writer,其具體子類是OutputStreamWriter和BufferedWriter。

             1)OutputStreamWriter

             OutputStreamWriter不是簡單地從FileOutputStream進行了編碼包裝,它是有緩衝的,但緩衝不是它自己實現的,而是依賴其組合的流編碼類自帶的。OutputStreamWriter源碼分析如下:          

複製代碼
public class OutputStreamWriter extends Writer {

// 流編碼類,所有操作都交給它完成。

private final StreamEncoder se;


// 創建使用指定字符的OutputStreamWriter。

public OutputStreamWriter(OutputStream out, String charsetName)


throws UnsupportedEncodingException

{


super(out);


if (charsetName == null)



throw new NullPointerException("charsetName");


se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);

}


// 創建使用默認字符的OutputStreamWriter。

public OutputStreamWriter(OutputStream out) {


super(out);


try {



se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);


} catch (UnsupportedEncodingException e) {



throw new Error(e);


}

}


// 創建使用指定字符集的OutputStreamWriter。

public OutputStreamWriter(OutputStream out, Charset cs) {


super(out);


if (cs == null)



throw new NullPointerException("charset");


se = StreamEncoder.forOutputStreamWriter(out, this, cs);

}


// 創建使用指定字符集編碼器的OutputStreamWriter。

public OutputStreamWriter(OutputStream out, CharsetEncoder enc) {


super(out);


if (enc == null)



throw new NullPointerException("charset encoder");


se = StreamEncoder.forOutputStreamWriter(out, this, enc);

}


// 返回該流使用的字符編碼名。如果流已經關閉,則此方法可能返回 null。

public String getEncoding() {


return se.getEncoding();

}


// 刷新輸出緩衝區到底層字節流,而不刷新字節流本身。該方法可以被PrintStream調用。

void flushBuffer() throws IOException {


se.flushBuffer();

}


// 寫入單個字符

public void write(int c) throws IOException {


se.write(c);

}


// 寫入字符數組的一部分

public void write(char cbuf[], int off, int len) throws IOException {


se.write(cbuf, off, len);

}


// 寫入字符串的一部分

public void write(String str, int off, int len) throws IOException {


se.write(str, off, len);

}


// 刷新該流。可以發現,刷新緩衝區其實是通過流編碼類的flush()實現的,故可以看出,緩衝區是流編碼類自帶的而不是OutputStreamWriter實現的。

public void flush() throws IOException {


se.flush();

}


// 關閉該流。

public void close() throws IOException {


se.close();

}
}
複製代碼

       每次調用 write() 方法都會導致在給定字符(或字符集)上調用編碼轉換器。在寫入底層輸出流之前,得到的這些字節將在緩衝區中累積(傳遞給 write() 方法的字符沒有緩衝,輸出數組纔有緩衝)。爲了獲得最高效率,可考慮將 OutputStreamWriter 包裝到 BufferedWriter 中,以避免頻繁調用轉換器。

 

        2)BufferedWriter

        帶緩衝的字符輸出流:與OutputStreamWriter的緩衝不同,BufferedWriter的緩衝是真正由自己創建的緩衝數組來實現的。故此:不需要頻繁調用編碼轉換器進行緩衝,而且,它可以提供單個字符、數組和字符串的緩衝(編碼轉換器只能緩衝字符數組和字符串)。

        BufferedWriter可以在創建時把一個OutputStreamWriter進行包裝,爲輸出流建立緩衝;

        然後,通過

複製代碼
void write(char[] cbuf, int off, int len) 
          寫入字符數組的某一部分。 
 void write(int c) 
          寫入單個字符。 
 void write(String s, int off, int len) 
          寫入字符串的某一部分。 
複製代碼

       向緩衝區寫入數據。

       還可以通過

 void newLine() 

       寫入一個行分隔符。 

       最後,可以手動控制緩衝區的數據刷新:

void flush() 刷新該流的緩衝。 

 

轉載請註明原文地址:http://www.cnblogs.com/ygj0930/p/5827509.html 

發佈了127 篇原創文章 · 獲贊 61 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章