Java BufferedOutputStream類的常用方法講解
BufferedOutputStream類的常用方法
BufferedOutputStream字節緩沖輸出流
構造方式
第一種開發中
public BufferedOutputStream(OutputStream out)
采用的默認的緩沖區大小(足夠大瞭) ,來構造一個字節緩沖輸出流對象
public BufferedOutputStream(OutputStream out,int size)
指定size緩沖區大小構造緩沖輸出流對象
IllegalArgumentException – 如果 size <= 0
常用方法
public void write(int b)throws IOException
一次寫一個字節
- b – 要寫入的字節。
public void write(byte[] b,int off,int len) throws IOException
一次寫一個字節數組的一部分
- b – 數據。
- off – 數據的起始偏移量。
- len – 要寫入的字節數。
public void flush() throws IOException
刷新此緩沖的輸出流。這迫使所有緩沖的輸出字節被寫出到底層輸出流中。
public void close() throws IOException
關閉此輸出流並釋放與此流有關的所有系統資源。
FilterOutputStream 的 close 方法先調用其 flush 方法,然後調用其基礎輸出流的 close 方法。
程序示例
public static void main(String[] args) throws Exception { //符合Java一種設計模式:裝飾者設計模式(過濾器:Filter) BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt")) ; //寫數據 bos.write("hello".getBytes()); //釋放資源 bos.close(); }
BufferedOutputStream深入分析
FileOutputStream和BufferedOutputStream都提供瞭一系列的將數據寫入文件的方式,並且我們都知道BufferedOutputStream要比直接使用FileOutputStream寫入速度要快,本文通過案例實際演示一下兩者的區別。
代碼準備
public class BufferFile { public static void main(String[] args) { //每次向文件中寫入一個8字節的數組 byte[] bytes = "1234567\n".getBytes(); //每隔100毫秒通過buffer的方式向文件中寫入數據 new Thread(() -> { System.out.println("buffer_while start..."); File file = new File("/var/file_test_data/out_buffer_while.txt"); FileOutputStream fileOutputStream; try { fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); while (true) { Thread.sleep(100); bufferedOutputStream.write(bytes); } } catch (Exception e) { e.printStackTrace(); } }).start(); //通過buffer的方式向文件中寫入1千萬次 new Thread(() -> { System.out.println("buffer_for start..."); File file = new File("/var/file_test_data/out_buffer_for.txt"); FileOutputStream fileOutputStream; try { fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); for (int i = 0; i < 10000000; i++) { bufferedOutputStream.write(bytes); } } catch (Exception e) { e.printStackTrace(); } System.out.println(new Date() + ": buffer_for end..."); }).start(); //通過file的方式向文件中寫入1千萬次 new Thread(() -> { System.out.println("file_for start..."); File file = new File("/var/file_test_data/out_file_for.txt"); FileOutputStream fileOutputStream; try { fileOutputStream = new FileOutputStream(file); for (int i = 0; i < 10000000; i++) { fileOutputStream.write(bytes); } } catch (Exception e) { e.printStackTrace(); } System.out.println(new Date() + ": file_for end..."); }).start(); } }
開始運行
強行停止後的運行結果
1、file和buffe寫入速度比較
兩者分別寫入1千萬次,時間上buffer比file快8秒,如果當寫入次數指數級增加時,buffer的優勢將更加明顯。
2、數據寫入完整性問題
buffer雖然要比file快,但是從最終數據上可以看出,buffer會丟數據
- 當第一個線程寫入時數據還未滿8kb時,強制停止java進程,最終out_buffer_while.txt沒有數據。
- 第二個線程,雖然最終代碼執行完畢,但是比較file方式,out_buffer_for.txt文件看起來也丟瞭一部分數據。
原因分析
當使用buffer讀寫文件時,數據並沒有直接被寫入磁盤,而是被緩存到一個字節數據中,這個字節數組的大小是8kb,默認情況下隻有當8kb被填充滿瞭以後,數據才會被一次性寫入磁盤,這樣一來就大大減少瞭系統調用的次數(file是每一次write都會產生系統調用),當然也正是因為buffer中的每一次write隻是寫入到內存中(JVM自身內存中),所以當數據未寫入磁盤前,如果JVM進程掛瞭,那麼就會造成數據丟失。
手動刷盤
為瞭解決數據丟失的問題,buf中提供瞭flush()方法,用戶可以自行決定合適將數據刷寫到磁盤中
- 如果你的flush()調用的非常頻繁,那就會退化為普通的file模式瞭。
- 如果你的flush()調用的又不太頻繁,那麼丟數據的可能性就比較高。
- 無論如何業務邏輯中數據寫完時,一定要調用一次flush(),確保緩沖區的數據刷到磁盤上。
將無限循環寫入的代碼註釋掉,在buf寫1千萬完成後,加上bufferedOutputStream.flush();
public class BufferFile { public static void main(String[] args) { //每次向文件中寫入一個8字節的數組 byte[] bytes = "1234567\n".getBytes(); //每隔100毫秒通過buffer的方式向文件中寫入數據 /*new Thread(() -> { System.out.println("buffer_while start..."); File file = new File("/var/file_test_data/out_buffer_while.txt"); FileOutputStream fileOutputStream; try { fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); while (true) { Thread.sleep(100); bufferedOutputStream.write(bytes); } } catch (Exception e) { e.printStackTrace(); } }).start();*/ //通過buffer的方式向文件中寫入1千萬次 new Thread(() -> { System.out.println("buffer_for start..."); File file = new File("/var/file_test_data/out_buffer_for.txt"); FileOutputStream fileOutputStream; try { fileOutputStream = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); for (int i = 0; i < 10000000; i++) { bufferedOutputStream.write(bytes); } bufferedOutputStream.flush(); } catch (Exception e) { e.printStackTrace(); } System.out.println(new Date() + ": buffer_for end..."); }).start(); //通過file的方式向文件中寫入1千萬次 new Thread(() -> { System.out.println("file_for start..."); File file = new File("/var/file_test_data/out_file_for.txt"); FileOutputStream fileOutputStream; try { fileOutputStream = new FileOutputStream(file); for (int i = 0; i < 10000000; i++) { fileOutputStream.write(bytes); } } catch (Exception e) { e.printStackTrace(); } System.out.println(new Date() + ": file_for end..."); }).start(); } }
這次再看數據寫入完整瞭
buffer源碼分析
類的機構圖
首先當創建一個BufferedOutputStream對象時,構造方法就初始化瞭緩沖的字節數組大小為8kb
protected byte buf[]; public BufferedOutputStream(OutputStream out) { this(out, 8192); } public BufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }
當調用buffer.write(b)時,調用的是父類FilterOutputStream的方法
public void write(byte b[]) throws IOException { //寫入的字節數組b,從0開始,一共要寫入的長度 write(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { if ((off | len | (b.length - (len + off)) | (off + len)) < 0) throw new IndexOutOfBoundsException(); //遍歷數組,一個字節一個字節的把數據寫入數組中 for (int i = 0 ; i < len ; i++) { write(b[off + i]); } } public synchronized void write(int b) throws IOException { //判斷字節長度是否超過buf.length,buf在初始化已經指定大小為8192,即8kb //如果超過則調用flushBuffer if (count >= buf.length) { flushBuffer(); } 把每一個字節寫入緩沖的buf數組中,並且統計值count++ buf[count++] = (byte)b; } private void flushBuffer() throws IOException { if (count > 0) { //真正的調用OutputStream,寫入數據到磁盤中 //寫入buf緩沖字節數組數據,從0下標開始,一直寫到count,即有多少寫多少。 out.write(buf, 0, count); count = 0; } }
關於buf緩沖數據大小設置
buffer提供瞭可以自定義緩沖大小的構造方法
public BufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; }
如果緩沖大小設置的比較大。
- 好處:進一步減少調用系統內核寫數據的方法,提高寫入速度,kafka的批寫入默認就是16kb寫一次。
- 壞處:1、丟失的數據可能會更多,2、要註意堆內存的消耗。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Java中IO流解析及代碼實例
- Java實現文件壓縮為zip和解壓zip壓縮包
- Java基於BIO實現文件上傳功能
- Java中的字節,字符輸出流與字節和字符輸入流的簡單理解
- Java IO之字節輸入輸出流詳解