天天看點

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

下面( )對象表示标準輸入對象流。_位元組流與緩沖流
java零基礎入門-進階特性篇(十二) IO 流 2

本章先來看兩大“流”派中的位元組流。位元組流相對字元流總體結構簡單一點,隻用記住它的4個最基本的操作類就可以了。下面一張圖來看看這四個基本的操作類。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

圖解

上面這張圖通過兩個方面進行劃分,一個是輸入和輸出,一個是具不具備緩沖功能。下面來具體分析一下。

不帶緩沖的輸入輸出
FileInputStream

首先在磁盤上建立一個txt檔案,我在D盤根目錄建立(檔案名為demo.txt),然後使用FileInputStream這個類來讀取這個檔案。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

讀取檔案

這是最基本的檔案讀取方法。這段代碼中有幾個地方要注意一下。首先是File類型。這個也是處理檔案的重要類型,下面先插個隊,來先介紹一下File。

File

File用來操作檔案,注意,這裡是操作檔案本身,而不是獲得檔案的内容,擷取檔案的内容就需要使用流了。比如上面的demo.txt檔案,可以用File類通過檔案在系統中的路徑擷取檔案,但File無法讀取demo.txt中的内容。通過檔案路徑建立File類型的對象以後,就可以通過一系列的API來操作檔案,比如常用的一些方法:

getName():用于傳回檔案名或者路徑。getPath():傳回對象的路徑。exists():判斷檔案是否存在等等。除了操作檔案,還可以操作檔案夾,比如mkdir()方法可以建立檔案夾,經常和exists方法一起使用,判斷是否需要建立檔案夾,如果需要的檔案夾不存在則建立它。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

File類

以上代碼就是File的基本操作,其他的操作可以參考API,這裡不再逐一示範。總之,記住一點,

File類型操作的是檔案本身,無法操作檔案内容

第二個要注意的是D:demo.txt這個路徑。我們通常使用windows作為編碼的系統,而windows中路徑分隔符是單個 ,但是在java代碼中,需要添加一個作為轉義符,這樣才能被java識别為路徑分隔符。注意,我這裡強調了windows系統,因為好死不死,在linux裡面的路徑分隔符恰恰是反的 / 。由于我們的代碼最終會放在伺服器上運作,是以我們不能将路徑寫死成隻有windows系統可以識别的 。我們需要一個在windows裡是 ,在linux裡是 / 的方法。

這個方法File類也幫我們做好了,就是File.separator,将上面的路徑改造為平台無關的寫法就是

"D:" + File.separator + "demo.txt"

平台無關的路徑分隔符:意思就是無論哪個平台都可以擷取正确的路徑分隔符,在windows下File.separator是 ,在linux下File.separator是 / 。

好了,File的基本操作介紹完了,下面繼續介紹流。使用File類型根據檔案路徑建立一個檔案的對象,然後用這個對象作為FileInputStream輸入流的構造器參數,建立一個輸入流。這樣就可以通過流來擷取檔案的内容了。上例中,通過while循環逐個位元組的讀取檔案中的内容,然後轉換為char類型進行輸出。

來看一下FileInputStream的構造器。FileInputStream有兩個我們常用的構造器,一個接受File類型參數,就是上例中的寫法。還有一個構造器接受一個字元串的參數,也就是檔案路徑。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

構造器

這裡順便複習一下this關鍵字,在構造器中的this表示調用這個類的另一個不同參數的構造器,來看看this後面括号表示的是什麼意思。如果參數中的檔案不為空,那麼就根據參數位址建立一個匿名檔案對象,然後調用下面這個參數為File類型的構造器,是以上例中可以省略掉File對象的建立,直接給流傳遞一個檔案路徑也是可以的,因為接受字元串的構造器也可以完成建立File類型對象的工作。

FileInputStream fis = new FileInputStream("D:demo.txt");

但是這種輸出方式有缺點,上一章介紹過中文編碼,由于中文數量過于龐大,是以根據編碼表中的編碼,一個漢字可能占用2個或者3個甚至4個位元組,這樣如果逐個位元組輸出的話,當需要輸出的内容是中文的時候,就會出現亂碼。因為可能隻輸出了二分之一個或者三分之一個中文,這樣沒法顯示一個完整的中文,隻能是亂碼。是以如果需要輸出一個正确的中文,需要對代碼進行改造。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

中文亂碼

改造的話就不能再是逐個位元組的輸出,而是需要将多個位元組放在一起,同時讀出來。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

無亂碼

這樣将多個位元組内容,通過String的構造器将位元組轉換為字元串,就可以正确的輸出中文了。

為什麼不讀取一個視訊或者圖檔,而要讀取一個文本檔案?文本檔案不是應該使用字元流嗎?

因為這裡使用文本檔案友善示範,如果讀取一個圖檔或者視訊,Eclipse沒有辦法來展示讀取的圖檔或者視訊,是以用文本檔案來做例子比較友善。

FileOutputStream

既然輸入流是讀取檔案的内容,那麼相對應的,輸出流就是将内容寫入到檔案中。下面來看看如何将内容寫入檔案。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

輸出流

首先看代碼,首先是系統無關的分隔符寫法,這裡沒有使用 而是使用File.separator替代。另外,和輸入流類似的,輸出流也有字元串參數的構造器。在這個構造器中,也有将檔案路徑轉為File對象的操作,是以這裡沒有建立File對象的過程。

與輸入流對應的,輸出流将字元轉為對應的int,然後逐個将int使用輸出流的write方法,寫入到檔案中。除了使用int類型寫入檔案,還可以使用位元組寫入檔案,這裡與輸入流操作類似,就不在過多解釋,各位可以參照上面輸入流的方法和API自行完成。

還是給點提示吧,字元串轉為位元組,可以使用getBytes()方法完成。上例中不再需要循環逐個讀入字元,而是将str轉為位元組,str.getBytes(),然後用輸出流fos調用write方法的重載方法write(str.getBytes())即可。

具有緩沖功能的輸入輸出

介紹完兩個最基本的輸入輸出流後,再來看看具有緩沖功能的流如何使用。在看代碼之前,首先要弄清楚,什麼是具有緩沖功能。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

緩沖流

上面講解的普通流是逐個位元組進行輸入或輸出,這樣雖然可以完成工作,但是在效率上有很大的問題。當我們将檔案讀取的時候,會先加載到記憶體,然而剛剛加載了一個位元組到記憶體,馬上又要告訴磁盤,喂~大兄弟,給我把這個位元組寫到磁盤上,我們知道磁盤的效率比記憶體要低很多的,在磁盤寫入的過程中,記憶體隻能幹瞪眼,當磁盤寫完一個位元組後,記憶體再把下一個位元組交給磁盤,喂~大兄弟,繼續寫下一個,然後記憶體又等着磁盤寫下一個位元組。

普通流效率低下的最大原因就在于此,頻繁的調用磁盤,導緻無法發揮記憶體速度快的優點。于是為了提高效率,緩沖流出現了。看看緩沖流緩沖了什麼?緩沖流并不是每一個位元組都要調用一次磁盤,而是根據設定的緩沖區大小,每當緩沖區滿了以後,再調用一次磁盤,比如上圖中,緩沖區設定為3,結果就是每次緩沖區有3個位元組的資料以後,再調用一次磁盤,這樣一來,調用磁盤的次數就減少了很多,使效率得到了很大的提升。檔案越大,緩沖流效率的提升越明顯。

下面來看一個例子,首先是普通流。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

普通流的檔案複制

這裡的普通流沒有設定緩沖區,逐個位元組進行檔案讀入和寫入,花了17秒完成5m檔案的複制。這裡要注意的是流是需要關閉的,如果不關閉流可能會出現資源被占用或者記憶體洩漏的問題,通常在finally中關閉流,避免導緻沒有執行到流的關閉就抛出異常導緻關閉流不成功。

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

緩沖流的檔案複制

使用緩沖流進行檔案的複制,可以看到檔案的複制效率提高了很多。緩沖流的建立,需要InputStream子類作為參數,除了将普通流外面包裝了一層,其他代碼與普通流沒有差別,這種包一層就能有更強功能的流,還有個名稱叫做進階流,這種包一層的做法,有種更優雅的名稱---“裝飾模式”。關于裝飾模式,後面章節來具體說明。

緩沖流自帶緩沖區,這個緩沖區多大?

下面( )對象表示标準輸入對象流。_位元組流與緩沖流

部分源碼

了解了普通流的用法,緩沖流用起來沒有什麼難度,它僅僅是包裝了一層而已,是以當我們需要對磁盤上的檔案進行讀寫操作的時候,建議使用緩沖流,效率要高很多。