日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

javaIO流之缓冲流

發布時間:2024/3/13 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 javaIO流之缓冲流 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 簡介
  • 1、字節緩沖流
    • 1.1構造方法
    • 1.2緩沖流的高效
    • 1.3為什么字節緩沖流會這么快?
    • 1.4byte & 0xFF
  • 2、字符緩沖流
    • 2.1構造方法
    • 2.2字符緩沖流特有方法
  • 3、練習

簡介

Java 的緩沖流是對字節流和字符流的一種封裝,通過在內存中開辟緩沖區來提高 I/O 操作的效率。Java 通過 BufferedInputStream 和 BufferedOutputStream 來實現字節流的緩沖,通過 BufferedReader 和 BufferedWriter 來實現字符流的緩沖。

緩沖流的工作原理是將數據先寫入緩沖區中,當緩沖區滿時再一次性寫入文件或輸出流,或者當緩沖區為空時一次性從文件或輸入流中讀取一定量的數據。這樣可以減少系統的 I/O 操作次數,提高系統的 I/O 效率,從而提高程序的運行效率。

1、字節緩沖流

BufferedInputStream 和 BufferedOutputStream 屬于字節緩沖流,強化了字節流 InputStream 和 OutputStream,關于字節流。

1.1構造方法

  • BufferedInputStream(InputStream in) :創建一個新的緩沖輸入流,注意參數類型為InputStream。
  • BufferedOutputStream(OutputStream out): 創建一個新的緩沖輸出流,注意參數類型為OutputStream。
    代碼示例如下:
// 創建字節緩沖輸入流,先聲明字節流 FileInputStream fps = new FileInputStream(b.txt); BufferedInputStream bis = new BufferedInputStream(fps)// 創建字節緩沖輸入流(一步到位) BufferedInputStream bis = new BufferedInputStream(new FileInputStream("b.txt"));// 創建字節緩沖輸出流(一步到位) BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));

1.2緩沖流的高效

我們通過復制一個 370M+ 的大文件,來測試緩沖流的效率。為了做對比,我們先用基本流來實現一下,代碼如下:

// 記錄開始時間 long start = System.currentTimeMillis(); // 創建流對象 try (FileInputStream fis = new FileInputStream("py.mp4");//exe文件夠大FileOutputStream fos = new FileOutputStream("copyPy.mp4")){// 讀寫數據int b;while ((b = fis.read()) != -1) {fos.write(b);} } // 記錄結束時間 long end = System.currentTimeMillis(); System.out.println("普通流復制時間:"+(end - start)+" 毫秒");

不好意思,我本機比較菜,10 分鐘還在復制中。切換到緩沖流試一下,代碼如下:

// 記錄開始時間 long start = System.currentTimeMillis(); // 創建流對象 try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("py.mp4"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copyPy.mp4"));){// 讀寫數據int b;while ((b = bis.read()) != -1) {bos.write(b);} } // 記錄結束時間 long end = System.currentTimeMillis(); System.out.println("緩沖流復制時間:"+(end - start)+" 毫秒");

只需要 8016 毫秒,如何更快呢?

可以換數組的方式來讀寫,這個我們前面也有講到,代碼如下:

// 記錄開始時間 long start = System.currentTimeMillis(); // 創建流對象 try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("py.mp4"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copyPy.mp4"));){// 讀寫數據int len;byte[] bytes = new byte[8*1024];while ((len = bis.read(bytes)) != -1) {bos.write(bytes, 0 , len);} } // 記錄結束時間 long end = System.currentTimeMillis(); System.out.println("緩沖流使用數組復制時間:"+(end - start)+" 毫秒");

這下就更快了,只需要 521 毫秒。

1.3為什么字節緩沖流會這么快?

傳統的 Java IO 是阻塞模式的,它的工作狀態就是“讀/寫,等待,讀/寫,等待。。。。。。”

字節緩沖流解決的就是這個問題:一次多讀點多寫點,減少讀寫的頻率,用空間換時間。

  • 減少系統調用次數:在使用字節緩沖流時,數據不是立即寫入磁盤或輸出流,而是先寫入緩沖區,當緩沖區滿時再一次性寫入磁盤或輸出流。這樣可以減少系統調用的次數,從而提高 I/O 操作的效率。
  • 減少磁盤讀寫次數:在使用字節緩沖流時,當需要讀取數據時,緩沖流會先從緩沖區中讀取數據,如果緩沖區中沒有足夠的數據,則會一次性從磁盤或輸入流中讀取一定量的數據。同樣地,當需要寫入數據時,緩沖流會先將數據寫入緩沖區,如果緩沖區滿了,則會一次性將緩沖區中的數據寫入磁盤或輸出流。這樣可以減少磁盤讀寫的次數,從而提高 I/O 操作的效率。
  • 提高數據傳輸效率:在使用字節緩沖流時,由于數據是以塊的形式進行傳輸,因此可以減少數據傳輸的次數,從而提高數據傳輸的效率。

我們來看 BufferedInputStream 的 read 方法:

public synchronized int read() throws IOException {if (pos >= count) { // 如果當前位置已經到達緩沖區末尾fill(); // 填充緩沖區if (pos >= count) // 如果填充后仍然到達緩沖區末尾,說明已經讀取完畢return -1; // 返回 -1 表示已經讀取完畢}return getBufIfOpen()[pos++] & 0xff; // 返回當前位置的字節,并將位置加 1 }

我們來看 BufferedInputStream 的 read 方法:

public synchronized int read() throws IOException {if (pos >= count) { // 如果當前位置已經到達緩沖區末尾fill(); // 填充緩沖區if (pos >= count) // 如果填充后仍然到達緩沖區末尾,說明已經讀取完畢return -1; // 返回 -1 表示已經讀取完畢}return getBufIfOpen()[pos++] & 0xff; // 返回當前位置的字節,并將位置加 1 }

這段代碼主要有兩部分:

  • fill():該方法會將緩沖 buf 填滿。
  • getBufIfOpen()[pos++] & 0xff:返回當前讀取位置 pos 處的字節(getBufIfOpen()返回的是 buffer 數組,是 byte 類型),并將其與 0xff 進行位與運算。這里的目的是將讀取到的字節 b 當做無符號的字節處理,因為 Java 的 byte 類型是有符號的,而將 b 與 0xff 進行位與運算,就可以將其轉換為無符號的字節,其范圍為 0 到 255。
byte & 0xFF 我們一會再細講。

再來看 FileInputStream 的 read 方法:

在這段代碼中,read0() 方法是一個本地方法,它的實現是由底層操作系統提供的,并不是 Java 語言實現的。在不同的操作系統上,read0() 方法的實現可能會有所不同,但是它們的功能都是相同的,都是用于讀取一個字節

再來看一下 BufferedOutputStream 的 write(byte b[], int off, int len) 方法:

public synchronized void write(byte b[], int off, int len) throws IOException {if (len >= buf.length) { // 如果寫入的字節數大于等于緩沖區長度/* 如果請求的長度超過了輸出緩沖區的大小,先刷新緩沖區,然后直接將數據寫入。這樣可以避免緩沖流級聯時的問題。*/flushBuffer(); // 先刷新緩沖區out.write(b, off, len); // 直接將數據寫入輸出流return;}if (len > buf.length - count) { // 如果寫入的字節數大于空余空間flushBuffer(); // 先刷新緩沖區}System.arraycopy(b, off, buf, count, len); // 將數據拷貝到緩沖區中count += len; // 更新計數器 }
  • 首先,該方法會檢查寫入的字節數是否大于等于緩沖區長度,如果是,則先將緩沖區中的數據刷新到磁盤中,然后直接將數據寫入輸出流。這樣做是為了避免緩沖流級聯時的問題,即緩沖區的大小不足以容納寫入的數據時,可能會引發級聯刷新,導致效率降低。

  • 級聯問題(Cascade Problem)是指在一組緩沖流(Buffered Stream)中,由于緩沖區的大小不足以容納要寫入的數據,導致數據被分割成多個部分,并分別寫入到不同的緩沖區中,最終需要逐個刷新緩沖區,從而導致性能下降的問題。

  • 其次,如果寫入的字節數小于緩沖區長度,則檢查緩沖區中剩余的空間是否足夠容納要寫入的字節數,如果不夠,則先將緩沖區中的數據刷新到磁盤中。然后,使用 System.arraycopy() 方法將要寫入的數據拷貝到緩沖區中,并更新計數器 count。

  • 最后,如果寫入的字節數小于緩沖區長度且緩沖區中還有剩余空間,則直接將要寫入的數據拷貝到緩沖區中,并更新計數器 count。

也就是說,只有當 buf 寫滿了,才會 flush,將數據刷到磁盤,默認一次刷 8192 個字節。

public BufferedOutputStream(OutputStream out) {this(out, 8192); }

如果 buf 沒有寫滿,會繼續寫 buf。

對比一下 FileOutputStream 的 write 方法,同樣是本地方法,一次只能寫入一個字節。
當把 BufferedOutputStream 和 BufferedInputStream 配合起來使用后,就減少了大量的讀寫次數,尤其是 byte[] bytes = new byte[8*1024],就相當于緩沖區的空間有 8 個 1024 字節,那讀寫效率就會大大提高。

1.4byte & 0xFF

byte 類型通常被用于存儲二進制數據,例如讀取和寫入文件、網絡傳輸等場景。在這些場景下,byte 類型的變量可以用來存儲數據流中的每個字節,從而進行讀取和寫入操作。

byte 類型是有符號的,即其取值范圍為 -128 到 127。如果我們希望得到的是一個無符號的 byte 值,就需要使用 byte & 0xFF 來進行轉換。

這是因為 0xFF 是一個無符號的整數,它的二進制表示為 11111111。當一個 byte 類型的值與 0xFF 進行位與運算時,會將 byte 類型的值轉換為一個無符號的整數,其范圍為 0 到 255。

0xff 是一個十六進制的數,相當于二進制的 11111111,& 運算符的意思是:如果兩個操作數的對應位為 1,則輸出 1,否則為 0;由于 0xff 有 8 個 1,單個 byte 轉成 int 其實就是將 byte 和 int 類型的 255 進行(&)與運算。

例如,如果我們有一個 byte 類型的變量 b,其值為 -1,那么 b & 0xFF 的結果就是 255。這樣就可以將一個有符號的 byte 類型的值轉換為一個無符號的整數。

& 運算是一種二進制數據的計算方式, 兩個操作位都為1,結果才為1,否則結果為0. 在上面的 getBufIfOpen()[pos++] & 0xff 計算過程中, byte 有 8bit, OXFF 是16進制的255, 表示的是 int 類型, int 有 32bit.

如果 getBufIfOpen()[pos++] 為 -118, 那么其原碼表示為

00000000 00000000 00000000 10001010

反碼為

11111111 11111111 11111111 11110101

補碼為

11111111 11111111 11111111 11110110

0XFF 表示16進制的數據255, 原碼, 反碼, 補碼都是一樣的, 其二進制數據為

00000000 00000000 00000000 11111111

0XFF 和 -118 進行&運算后結果為

00000000 00000000 00000000 11110110

還原為原碼后為

00000000 00000000 00000000 10001010

其表示的 int 值為 138,可見將 byte 類型的 -118 與 0XFF 進行與運算后值由 -118 變成了 int 類型的 138,其中低8位和byte的-118完全一致。

順帶聊一下 原碼、反碼和補碼。

①、原碼
原碼就是符號位加上真值的絕對值,即用第一位表示符號,其余位表示值。比如如果是8位二進制:

[+1]= 0000 0001[-1]= 1000 0001

第一位是符號位。因為第一位是符號位,所以8位二進制數的取值范圍就是:

[1111 1111 , 0111 1111]


[-127 , 127]

②、反碼

反碼的表示方法是:

正數的反碼是其本身
負數的反碼是在其原碼的基礎上,符號位不變,其余各個位取反。
例如:

[+1] = [00000001]= [00000001][-1] = [10000001]= [11111110]

可見如果一個反碼表示的是負數,人腦無法直觀的看出來它的數值。通常要將其轉換成原碼再計算。

③、補碼

補碼的表示方法是:

正數的補碼就是其本身
負數的補碼是在其原碼的基礎上,符號位不變,其余各位取反,最后+1。(即在反碼的基礎上+1)

[+1] = [00000001]= [00000001]= [00000001][-1] = [10000001]= [11111110]= [11111111]

對于負數,補碼表示方式也是人腦無法直觀看出其數值的。通常也需要轉換成原碼在計算其數值。

從上面可以看到:

對于正數:原碼,反碼,補碼都是一樣的
對于負數:原碼,反碼,補碼都是不一樣的

2、字符緩沖流

BufferedReader 類繼承自 Reader 類,提供了一些便捷的方法,例如 readLine() 方法可以一次讀取一行數據,而不是一個字符一個字符地讀取。

BufferedWriter 類繼承自 Writer 類,提供了一些便捷的方法,例如 newLine() 方法可以寫入一個系統特定的行分隔符。

2.1構造方法

BufferedReader(Reader in) :創建一個新的緩沖輸入流,注意參數類型為Reader。
BufferedWriter(Writer out): 創建一個新的緩沖輸出流,注意參數類型為Writer。
代碼示例如下:

// 創建字符緩沖輸入流 BufferedReader br = new BufferedReader(new FileReader("b.txt")); // 創建字符緩沖輸出流 BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));

2.2字符緩沖流特有方法

字符緩沖流的基本方法與普通字符流調用方式一致,這里不再贅述,我們來看字符緩沖流特有的方法。

BufferedReader:String readLine(): 讀一行數據,讀取到最后返回 null
BufferedWriter:newLine(): 換行,由系統定義換行符。
來看 readLine()方法的代碼示例:

// 創建流對象 BufferedReader br = new BufferedReader(new FileReader("a.txt")); // 定義字符串,保存讀取的一行文字 String line = null; // 循環讀取,讀取到最后返回null while ((line = br.readLine())!=null) {System.out.print(line);System.out.println("------"); } // 釋放資源 br.close();

再來看 newLine() 方法的代碼示例:

// 創建流對象 BfferedWriter bw = new BufferedWriter(new FileWriter("b.txt")); // 寫出數據 bw.write("追"); // 寫出換行 bw.newLine(); bw.write("風"); bw.newLine(); bw.write("少"); bw.newLine(); bw.write("年"); bw.newLine(); // 釋放資源 bw.close();

3、練習

來欣賞一下我寫的這篇詩:

6.岑夫子,丹丘生,將進酒,杯莫停。 1.君不見黃河之水天上來,奔流到海不復回。 8.鐘鼓饌玉不足貴,但愿長醉不愿醒。 3.人生得意須盡歡,莫使金樽空對月。 5.烹羊宰牛且為樂,會須一飲三百杯。 2.君不見高堂明鏡悲白發,朝如青絲暮成雪。 7.與君歌一曲,請君為我傾耳聽。 4.天生我材必有用,千金散盡還復來。

欣賞完了沒?
估計你也看出來了,這是李白寫的《將進酒》,不是我寫的。😝
不過,順序是亂的,還好,我都編了號。那如何才能按照正確的順序來呢?
來看代碼實現:

// 創建map集合,保存文本數據,鍵為序號,值為文字 HashMap<String, String> lineMap = new HashMap<>();// 創建流對象 源 BufferedReader br = new BufferedReader(new FileReader("logs/test.log")); //目標 BufferedWriter bw = new BufferedWriter(new FileWriter("logs/test1.txt"));// 讀取數據 String line; while ((line = br.readLine())!=null) {// 解析文本if (line.isEmpty()) {continue;}String[] split = line.split(Pattern.quote("."));// 保存到集合lineMap.put(split[0], split[1]); } // 釋放資源 br.close();// 遍歷map集合 for (int i = 1; i <= lineMap.size(); i++) {String key = String.valueOf(i);// 獲取map中文本String value = lineMap.get(key);// 寫出拼接文本bw.write(key+"."+value);// 寫出換行bw.newLine(); } // 釋放資源 bw.close();

來看輸出結果:

1.君不見黃河之水天上來,奔流到海不復回。 2.君不見高堂明鏡悲白發,朝如青絲暮成雪。 3.人生得意須盡歡,莫使金樽空對月。 4.天生我材必有用,千金散盡還復來。 5.烹羊宰牛且為樂,會須一飲三百杯。 6.岑夫子,丹丘生,將進酒,杯莫停。 7.與君歌一曲,請君為我傾耳聽。 8.鐘鼓饌玉不足貴,但愿長醉不愿醒。

相關文章鏈接:
javaIO之各種流的分類與實際應用
javaIO流之文件流
javaIO流之字節流
javaIO流之字符流
javaIO流之緩沖流
javaIO流之轉換流
javaIO流之序列流

吾之榮耀,離別已久。如果覺得有用,點個贊吧~~~~

總結

以上是生活随笔為你收集整理的javaIO流之缓冲流的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。