天天看點

Java IO流學習總結三:緩沖流-BufferedInputStream、BufferedOutputStreamJava IO流學習總結三:緩沖流-BufferedInputStream、BufferedOutputStream

原文位址:https://blog.csdn.net/zhaoyanjun6/article/details/54894451

Java IO流學習總結三:緩沖流-BufferedInputStream、BufferedOutputStream

轉載請标明出處:http://blog.csdn.net/zhaoyanjun6/article/details/54894451 

本文出自【趙彥軍的部落格】

InputStream
|__FilterInputStream
        |__BufferedInputStream
           

首先抛出一個問題,有了

InputStream

為什麼還要有

BufferedInputStream

?

BufferedInputStream

BufferedOutputStream

這兩個類分别是

FilterInputStream

FilterOutputStream

的子類,作為裝飾器子類,使用它們可以防止每次讀取/發送資料時進行實際的寫操作,代表着使用緩沖區。

我們有必要知道不帶緩沖的操作,每讀一個位元組就要寫入一個位元組,由于涉及磁盤的IO操作相比記憶體的操作要慢很多,是以不帶緩沖的流效率很低。帶緩沖的流,可以一次讀很多位元組,但不向磁盤中寫入,隻是先放到記憶體裡。等湊夠了緩沖區大小的時候一次性寫入磁盤,這種方式可以減少磁盤操作次數,速度就會提高很多!

同時正因為它們實作了緩沖功能,是以要注意在使用

BufferedOutputStream

寫完資料後,要調用

flush()

方法或

close()

方法,強行将緩沖區中的資料寫出。否則可能無法寫出資料。與之相似還

BufferedReader

BufferedWriter

兩個類。

現在就可以回答在本文的開頭提出的問題:

BufferedInputStream

BufferedOutputStream

類就是實作了緩沖功能的輸入流/輸出流。使用帶緩沖的輸入輸出流,效率更高,速度更快。

總結:

  • BufferedInputStream

     是緩沖輸入流。它繼承于

    FilterInputStream

  • BufferedInputStream

     的作用是為另一個輸入流添加一些功能,例如,提供“緩沖功能”以及支援

    mark()标記

    reset()重置方法

  • BufferedInputStream

     本質上是通過一個内部緩沖區數組實作的。例如,在建立某輸入流對應的

    BufferedInputStream

    後,當我們通過

    read()

    讀取輸入流的資料時,

    BufferedInputStream

    會将該輸入流的資料分批的填入到緩沖區中。每當緩沖區中的資料被讀完之後,輸入流會再次填充資料緩沖區;如此反複,直到我們讀完輸入流資料位置。

BufferedInputStream API簡介

源碼關鍵字段分析

private static int defaultBufferSize = ;//内置緩存位元組數組的大小 8KB

protected volatile byte buf[];  //内置緩存位元組數組

protected int count;    //目前buf中的位元組總數、注意不是底層位元組輸入流的源中位元組總數

protected int pos;      //目前buf中下一個被讀取的位元組下标

protected int markpos = -; //最後一次調用mark(int readLimit)方法記錄的buf中下一個被讀取的位元組的位置

protected int marklimit;    //調用mark後、在後續調用reset()方法失敗之前雲尋的從in中讀取的最大資料量、用于限制被标記後buffer的最大值
           

構造函數

BufferedInputStream(InputStream in) //使用預設buf大小、底層位元組輸入流建構bis 

BufferedInputStream(InputStream in, int size) //使用指定buf大小、底層位元組輸入流建構bis  
           

一般方法介紹

int available();  //傳回底層流對應的源中有效可供讀取的位元組數      

void close();  //關閉此流、釋放與此流有關的所有資源  

boolean markSupport();  //檢視此流是否支援mark

void mark(int readLimit); //标記目前buf中讀取下一個位元組的下标  

int read();  //讀取buf中下一個位元組  

int read(byte[] b, int off, int len);  //讀取buf中下一個位元組  

void reset();   //重置最後一次調用mark标記的buf中的位子  

long skip(long n);  //跳過n個位元組、 不僅僅是buf中的有效位元組、也包括in的源中的位元組 
           

BufferedOutputStream API簡介

關鍵字段

protected byte[] buf;   //内置緩存位元組數組、用于存放程式要寫入out的位元組  

protected int count;   //内置緩存位元組數組中現有位元組總數 
           

構造函數

BufferedOutputStream(OutputStream out); //使用預設大小、底層位元組輸出流構造bos。預設緩沖大小是 8192 位元組( 8KB )

BufferedOutputStream(OutputStream out, int size);  //使用指定大小、底層位元組輸出流構造bos  
           

構造函數源碼:

/**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream.
  * @param   out   the underlying output stream.
  */
 public BufferedOutputStream(OutputStream out) {
     this(out, );
 }

 /**
  * Creates a new buffered output stream to write data to the
  * specified underlying output stream with the specified buffer
  * size.
  *
  * @param   out    the underlying output stream.
  * @param   size   the buffer size.
  * @exception IllegalArgumentException if size <= 0.
  */
 public BufferedOutputStream(OutputStream out, int size) {
     super(out);
     if (size <= ) {
         throw new IllegalArgumentException("Buffer size <= 0");
     }
     buf = new byte[size];
 }
           

一般方法

//在這裡提一句,`BufferedOutputStream`沒有自己的`close`方法,當他調用父類`FilterOutputStrem`的方法關閉時,會間接調用自己實作的`flush`方法将buf中殘存的位元組flush到out中,再`out.flush()`到目的地中,DataOutputStream也是如此。 

void  flush();  将寫入bos中的資料flush到out指定的目的地中、注意這裡不是flush到out中、因為其内部又調用了out.flush()  

write(byte b);      将一個位元組寫入到buf中  

write(byte[] b, int off, int len);      将b的一部分寫入buf中 
           

那麼什麼時候flush()才有效呢? 

答案是:當OutputStream是BufferedOutputStream時。

當寫檔案需要flush()的效果時,需要 

FileOutputStream fos = new FileOutputStream(“c:\a.txt”); 

BufferedOutputStream bos = new BufferedOutputStream(fos); 

也就是說,需要将FileOutputStream作為BufferedOutputStream構造函數的參數傳入,然後對BufferedOutputStream進行寫入操作,才能利用緩沖及flush()。

檢視BufferedOutputStream的源代碼,發現所謂的buffer其實就是一個byte[]。 

BufferedOutputStream的每一次write其實是将内容寫入byte[],當buffer容量到達上限時,會觸發真正的磁盤寫入。 

而另一種觸發磁盤寫入的辦法就是調用flush()了。

1.

BufferedOutputStream

close()

時會自動flush 

2.

BufferedOutputStream

在不調用

close()

的情況下,緩沖區不滿,又需要把緩沖區的内容寫入到檔案或通過網絡發送到别的機器時,才需要調用flush.

實戰演練1:複制檔案. 

操作:使用緩存流将F盤根目錄裡面名字為:123.png 圖檔複制成 abc.png

package com.app;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


public class A3 {

    public static void main(String[] args) throws IOException {

        String filePath = "F:/123.png" ;
        String filePath2 = "F:/abc.png" ;
        File file = new File( filePath ) ;
        File file2 = new File( filePath2 ) ;
        copyFile( file , file2 );

    }

    /**
     * 複制檔案
     * @param oldFile
     * @param newFile
     */
    public static void copyFile( File oldFile , File newFile){
        InputStream inputStream = null ;
        BufferedInputStream bufferedInputStream = null ;

        OutputStream outputStream = null ;
        BufferedOutputStream bufferedOutputStream = null ;

        try {
            inputStream = new FileInputStream( oldFile ) ;
            bufferedInputStream = new BufferedInputStream( inputStream ) ;

            outputStream = new FileOutputStream( newFile ) ;
            bufferedOutputStream = new BufferedOutputStream( outputStream ) ;

            byte[] b=new byte[];   //代表一次最多讀取1KB的内容

            int length =  ; //代表實際讀取的位元組數
            while( (length = bufferedInputStream.read( b ) )!= - ){
                //length 代表實際讀取的位元組數
                bufferedOutputStream.write(b, , length );
            }
            //緩沖區的内容寫入到檔案
            bufferedOutputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();
        }finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( inputStream != null ){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if ( outputStream != null ) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}
           

效果圖:

Java IO流學習總結三:緩沖流-BufferedInputStream、BufferedOutputStreamJava IO流學習總結三:緩沖流-BufferedInputStream、BufferedOutputStream

如何正确的關閉流

在上面的代碼中,我們關閉流的代碼是這樣寫的。

finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( inputStream != null ){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if ( outputStream != null ) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
           

思考:在處理流關閉完成後,我們還需要關閉節點流嗎?

讓我們帶着問題去看源碼: 

bufferedOutputStream.close();

/**
     * Closes this input stream and releases any system resources
     * associated with the stream.
     * Once the stream has been closed, further read(), available(), reset(),
     * or skip() invocations will throw an IOException.
     * Closing a previously closed stream has no effect.
     *
     * @exception  IOException  if an I/O error occurs.
     */
    public void close() throws IOException {
        byte[] buffer;
        while ( (buffer = buf) != null) {
            if (bufUpdater.compareAndSet(this, buffer, null)) {
                InputStream input = in;
                in = null;
                if (input != null)
                    input.close();
                return;
            }
            // Else retry in case a new buf was CASed in fill()
        }
    }
           

close()方法的作用 

1、關閉輸入流,并且釋放系統資源 

2、BufferedInputStream裝飾一個 InputStream 使之具有緩沖功能,is要關閉隻需要調用最終被裝飾出的對象的 close()方法即可,因為它最終會調用真正資料源對象的 close()方法。是以,可以隻調用外層流的close方法關閉其裝飾的内層流。

那麼如果我們想逐個關閉流,我們該怎麼做?

答案是:先關閉外層流,再關閉内層流。一般情況下是:先打開的後關閉,後打開的先關閉;另一種情況:看依賴關系,如果流a依賴流b,應該先關閉流a,再關閉流b。例如處理流a依賴節點流b,應該先關閉處理流a,再關閉節點流b

看懂了怎麼正确的關閉流之後,那麼我們就可以優化上面的代碼了,隻關閉外層的處理流。

finally {

            if( bufferedOutputStream != null ){
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if( bufferedInputStream != null){
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }