原文位址: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();
}
}
}
}
}
效果圖:
如何正确的關閉流
在上面的代碼中,我們關閉流的代碼是這樣寫的。
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();
}
}
}