天天看點

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

本章内容包括3個部分:BufferedInputStream介紹,BufferedInputStream源碼,以及BufferedInputStream使用示例。

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

BufferedInputStream 本質上是通過一個内部緩沖區數組實作的。例如,在

建立某輸入流對應的BufferedInputStream後,當我們通過read()讀取輸入流的資料時,BufferedInputStream會将

該輸入流的資料分批的填入到緩沖區中。每當緩沖區中的資料被讀完之後,輸入流會再次填充資料緩沖區;如此反複,直到我們讀完輸入流資料位置。

BufferedInputStream 函數清單

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

說明:

想讀懂BufferedInputStream的源碼,就要先了解它的思想。BufferedInputStream的作用是為其它輸入流提供緩沖功能。

建立BufferedInputStream時,我們會通過它的構造函數指定某個輸入流為參數。BufferedInputStream會将該輸入流資料

分批讀取,每次讀取一部分到緩沖中;操作完緩沖中的這部分資料之後,再從輸入流中讀取下一部分的資料。

為什麼需要緩沖呢?原因很簡單,效率問題!緩沖中的資料實際上是儲存在記憶體中,而原始資料可能是儲存在硬碟或NandFlash等存儲媒體中;而我們知道,從記憶體中讀取資料的速度比從硬碟讀取資料的速度至少快10倍以上。

那幹嘛不幹脆一次性将全部資料都讀取到緩沖中呢?第一,讀取全部的資料所需要的時間可能會很長。第二,記憶體價格很貴,容量不像硬碟那麼大。

下面,我就BufferedInputStream中最重要的函數fill()進行說明。其它的函數很容易了解,我就不詳細介紹了,大家可以參考源碼中的注釋進行了解。

fill() 源碼如下:

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

根據fill()中的if...else...,下面我們将fill分為5種情況進行說明。

情況1:讀取完buffer中的資料,并且buffer沒有被标記

執行流程如下,

(01) read() 函數中調用 fill()

(02) fill() 中的 if (markpos < 0) ...

為了友善分析,我們将這種情況下fill()執行的操作等價于以下代碼:

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

這種情況發生的情況是 — — 輸入流中有很長的資料,我們每次從中讀取一部分資料到buffer中進行操作。每次當我們讀取完buffer中的資料之後,并且此時輸入流沒有被标記;那麼,就接着從輸入流中讀取下一部分的資料到buffer中。

其中,判斷是否讀完buffer中的資料,是通過 if (pos >= count) 來判斷的;

          判斷輸入流有沒有被标記,是通過 if (markpos < 0) 來判斷的。

了解這個思想之後,我們再對這種情況下的fill()的代碼進行分析,就特别容易了解了。

(01) if (markpos < 0) 它的作用是判斷“輸入流是否被标記”。若被标記,則markpos大于/等于0;否則markpos等于-1。

(02) 在這種情況下:通過getInIfOpen()擷取輸入流,然後接着從輸入流中讀取buffer.length個位元組到buffer中。

(03) count = n + pos; 這是根據從輸入流中讀取的實際資料的多少,來更新buffer中資料的實際大小。

情況2:讀取完buffer中的資料,buffer的标記位置>0,并且buffer中沒有多餘的空間

(02) fill() 中的 else if (pos >= buffer.length) ...

(03) fill() 中的 if (markpos > 0) ...

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

種情況發生的情況是 — —

輸入流中有很長的資料,我們每次從中讀取一部分資料到buffer中進行操作。當我們讀取完buffer中的資料之後,并且此時輸入流存在标記時;那麼,

就發生情況2。此時,我們要保留“被标記位置”到“buffer末尾”的資料,然後再從輸入流中讀取下一部分的資料到buffer中。

          判斷buffer中沒有多餘的空間,是通過 if (pos >= buffer.length) 來判斷的。

了解這個思想之後,我們再對這種情況下的fill()代碼進行分析,就特别容易了解了。

(01) int sz = pos - markpos; 作用是“擷取‘被标記位置’到‘buffer末尾’”的資料長度。

(02)

System.arraycopy(buffer, markpos, buffer, 0, sz);

作用是“将buffer中從markpos開始的資料”拷貝到buffer中(從位置0開始填充,填充長度是sz)。接着,将sz指派給pos,即pos

就是“被标記位置”到“buffer末尾”的資料長度。

(03) int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 從輸入流中讀取出“buffer.length - pos”的資料,然後填充到buffer中。

(04) 通過第(02)和(03)步組合起來的buffer,就是包含了“原始buffer被标記位置到buffer末尾”的資料,也包含了“從輸入流中新讀取的資料”。

注意:執行過情況2之後,markpos的值由“大于0”變成了“等于0”!

情況3:讀取完buffer中的資料,buffer被标記位置=0,buffer中沒有多餘的空間,并且buffer.length>=marklimit

(03) fill() 中的 else if (buffer.length >= marklimit) ...

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

說明:這種情況的處理非常簡單。首先,就是“取消标記”,即 markpos = -1;然後,設定初始化位置為0,即pos=0;最後,再從輸入流中讀取下一部分資料到buffer中。

情況4:讀取完buffer中的資料,buffer被标記位置=0,buffer中沒有多餘的空間,并且buffer.length<marklimit

(03) fill() 中的 else { int nsz = pos * 2; ... }

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

這種情況的處理非常簡單。

(01) 建立一個位元組數組nbuf。nbuf的大小是“pos*2”和“marklimit”中較小的那個數。

(02) 接着,将buffer中的資料拷貝到新數組nbuf中。通過System.arraycopy(buffer, 0, nbuf, 0, pos)

(03) 最後,從輸入流讀取部分新資料到buffer中。通過getInIfOpen().read(buffer, pos, buffer.length - pos);

注意:在這裡,我們思考一個問題,“為什麼需要marklimit,它的存在到底有什麼意義?”我們結合“情況2”、“情況3”、“情況4”的情況來分析。

假設,marklimit是無限大的,而且我們設定了markpos。當我們從輸

入流中每讀完一部分資料并讀取下一部分資料時,都需要儲存markpos所标記的資料;這就意味着,我們需要不斷執行情況4中的操作,要将buffer的

容量擴大……随着讀取次數的增多,buffer會越來越大;這會導緻我們占據的記憶體越來越大。是以,我們需要給出一個marklimit;當

buffer>=marklimit時,就不再儲存markpos的值了。

情況5:除了上面4種情況之外的情況

(02) fill() 中的 count = pos...

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

說明:這種情況的處理非常簡單。直接從輸入流讀取部分新資料到buffer中。

關于BufferedInputStream中API的詳細用法,參考示例代碼(BufferedInputStreamTest.java):

java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例
java io系列12之 BufferedInputStream(緩沖輸入流)的認知、源碼和示例

程式中讀取的bufferedinputstream.txt的内容如下:

運作結果:

0 : 0x61

1 : 0x62

2 : 0x63

3 : 0x64

4 : 0x65

str1=01234

str2=fghij