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


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

轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/54894451
本文出自【趙彥軍的博客】

InputStream
|__FilterInputStream
        |__BufferedInputStream

首先拋出一個問題,有了InputStream為什么還要有BufferedInputStream?

BufferedInputStreamBufferedOutputStream這兩個類分別是FilterInputStreamFilterOutputStream的子類,作為裝飾器子類,使用它們可以防止每次讀取/發送數據時進行實際的寫操作,代表着使用緩沖區。

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

同時正因為它們實現了緩沖功能,所以要注意在使用BufferedOutputStream寫完數據后,要調用flush()方法或close()方法,強行將緩沖區中的數據寫出。否則可能無法寫出數據。與之相似還BufferedReaderBufferedWriter兩個類。

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

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

總結:

  • BufferedInputStream 是緩沖輸入流。它繼承於FilterInputStream

  • BufferedInputStream 的作用是為另一個輸入流添加一些功能,例如,提供“緩沖功能”以及支持mark()標記reset()重置方法

  • BufferedInputStream 本質上是通過一個內部緩沖區數組實現的。例如,在新建某輸入流對應的BufferedInputStream后,當我們通過read()讀取輸入流的數據時,BufferedInputStream會將該輸入流的數據分批的填入到緩沖區中。每當緩沖區中的數據被讀完之后,輸入流會再次填充數據緩沖區;如此反復,直到我們讀完輸入流數據位置。

BufferedInputStream API簡介

源碼關鍵字段分析


private static int defaultBufferSize = 8192;//內置緩存字節數組的大小 8KB
	
protected volatile byte buf[];	//內置緩存字節數組
	
protected int count;	//當前buf中的字節總數、注意不是底層字節輸入流的源中字節總數
	
protected int pos;		//當前buf中下一個被讀取的字節下標
	
protected int markpos = -1;	//最后一次調用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, 8192);
 }

 /**
  * 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 <= 0) {
         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.BufferedOutputStreamclose()時會自動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[1024];   //代表一次最多讀取1KB的內容

			int length = 0 ; //代表實際讀取的字節數
			while( (length = bufferedInputStream.read( b ) )!= -1 ){
				//length 代表實際讀取的字節數
				bufferedOutputStream.write(b, 0, 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();
				}
			}

		}
	}
}

效果圖:

這里寫圖片描述

如何正確的關閉流

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

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();
				}
			}
		}

個人微信號:zhaoyanjun125 , 歡迎關注


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM