轉載:http://www.cnblogs.com/zhaoyanjun/p/6376937.html
http://blog.csdn.net/dabing69221/article/details/16996877
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 = 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.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[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();
}
}
}
為什么要flush
與在網絡硬件中緩存一樣,流還可以在軟件中得到緩存,即直接在Java代碼中緩存。這可以通過BufferedOutputStream或BufferedWriter 鏈接到底層流上來實現。因此,在寫完數據時,flush就顯得尤為重要。比如:
上圖中WEB服務器通過輸出流向客戶端響應了一個300字節的信息,但是,這時的輸出流有一個1024字節的緩沖區。所以,輸出流就一直等著WEB服務器繼續向客戶端響應信 息,當WEB服務器的響應信息把輸出流中的緩沖區填滿時,這時,輸出流才向WEB客戶端響應消息。
為了解決這種尷尬的局面,flush()方法出現了。flush()方法可以強迫輸出流(或緩沖的流)發送數據,即使此時緩沖區還沒有填滿,以此來打破這種死鎖的狀態。
當我們使用輸出流發送數據時,當數據不能填滿輸出流的緩沖區時,這時,數據就會被存儲在輸出流的緩沖區中。如果,我們這個時候調用關閉(close)輸出流,存儲在輸出流的緩沖區中的數據就會丟失。所以說,關閉(close)輸出流時,應先刷新(flush)換沖的輸出流,話句話說就是:“迫使所有緩沖的輸出數據被寫出到底層輸出流中”。