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子類作為參數,除了將普通流外面包裝了一層,其他代碼與普通流沒有區別,這種包一層就能有更強功能的流,還有個名稱叫做高級流,這種包一層的做法,有種更優雅的名稱---「裝飾模式」。關於裝飾模式,後面章節來具體說明。

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

部分源碼

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

推薦閱讀:

相关文章