天天看點

Java IO

java io即java 輸入輸出系統。不管我們編寫何種應用,都難免和各種輸入輸出相關的媒介打交道,其實和媒介進行io的過程是十分複雜的,這要考慮的因素特别多,比如我們要考慮和哪種媒介進行io(檔案、控制台、網絡),我們還要考慮具體和它們的通信方式(順序、随機、二進制、按字元、按字、按行等等)。java類庫的設計者通過設計大量的類來攻克這些難題,這些類就位于java.io包中。

在jdk1.4之後,為了提高java io的效率,java又提供了一套新的io,java new io簡稱java nio。它在标準java代碼中提供了高速的面向塊的io操作。本篇文章重點介紹java io,關于java nio請參考我的另兩篇文章:

<a href="http://blog.csdn.net/suifeng3051/article/details/48160753">java nio詳解(一)</a>

<a href="http://blog.csdn.net/suifeng3051/article/details/48441629">java nio詳解(二)</a>

在java io中,流是一個核心的概念。流從概念上來說是一個連續的資料流。你既可以從流中讀取資料,也可以往流中寫資料。流與資料源或者資料流向的媒介相關聯。在java io中流既可以是位元組流(以位元組為機關進行讀寫),也可以是字元流(以字元為機關進行讀寫)。

java的io包主要關注的是從原始資料源的讀取以及輸出原始資料到目标媒介。以下是最典型的資料源和目标媒介:

檔案

管道

網絡連接配接

記憶體緩存

system.in, system.out, system.error(注:java标準輸入、輸出、錯誤輸出)

雖然java io類庫龐大,但總體來說其架構還是很清楚的。從是讀媒介還是寫媒介的次元看,java io可以分為:

輸入流:inputstream和reader

輸出流:outputstream和writer

而從其處理流的類型的次元上看,java io又可以分為:

位元組流:inputstream和outputstream

字元流:reader和writer

下面這幅圖就清晰的描述了javaio的分類:

-

位元組流

字元流

輸入流

inputstream

reader

輸出流

outputstream

writer

我們的程式需要通過inputstream或reader從資料源讀取資料,然後用outputstream或者writer将資料寫入到目标媒介中。其中,inputstream和reader與資料源相關聯,outputstream和writer與目标媒介相關聯。 以下的圖說明了這一點:

Java IO

上面我們介紹了java io中的四各類:inputstream、outputstream、reader、writer,其實在我們的實際應用中,我們用到的一般是它們的子類,之是以設計這麼多子類,目的就是讓每一個類都負責不同的功能,以友善我們開發各種應用。各類用途彙總如下:

檔案通路

網絡通路

記憶體緩存通路

線程内部通信(管道)

緩沖

過濾

解析

讀寫文本 (readers / writers)

讀寫基本類型資料 (long, int etc.)

讀寫對象

下面我們就通過兩張圖來大體了解一下這些類的繼承關系及其作用

圖1:java io 類的內建關系

Java IO

圖2:java io中各個類所負責的媒介

Java IO

通過上面的介紹我們已經知道,位元組流對應的類應該是inputstream和outputstream,而在我們實際開發中,我們應該根據不同的媒介類型選用相應的子類來處理。下面我們就用位元組流來操作檔案媒介:

例1,用位元組流寫檔案

例2,用位元組流讀檔案

同樣,字元流對應的類應該是reader和writer。下面我們就用字元流來操作檔案媒介:

例3,用字元流讀檔案

例4,用字元流寫檔案

位元組流可以轉換成字元流,java.io包中提供的inputstreamreader類就可以實作,當然從其命名上就可以看出它的作用。其實這涉及到另一個概念,io流的組合,後面我們詳細介紹。下面看一個簡單的例子:

例5 ,位元組流轉換為字元流

從上面位元組流轉換成字元流的例子中我們知道了io流之間可以組合(或稱嵌套),其實組合的目的很簡單,就是把多種類的特性融合在一起以實作更多的功能。組合使用的方式很簡單,通過把一個流放入另一個流的構造器中即可實作,兩個流之間可以組合,三個或者更多流之間也可組合到一起。當然,并不是任意流之間都可以組合。關于組合就不過多介紹了,後面的例子中有很多都用到了組合,大家好好體會即可。

file是java io中最常用的讀寫媒介,那麼我們在這裡就對檔案再做進一步介紹。

例6 ,file操作

}

通過上面的例子我們已經知道,我們可以用fileinputstream(檔案字元流)或filereader(檔案位元組流)來讀檔案,這兩個類可以讓我們分别以字元和位元組的方式來讀取檔案内容,但是它們都有一個不足之處,就是隻能從檔案頭開始讀,然後讀到檔案結束。

但是有時候我們隻希望讀取檔案的一部分,或者是說随機的讀取檔案,那麼我們就可以利用randomaccessfile。randomaccessfile提供了<code>seek()</code>方法,用來定位将要讀寫檔案的指針位置,我們也可以通過調用<code>getfilepointer()</code>方法來擷取目前指針的位置,具體看下面的例子:

例7,随機讀取檔案

例8,随機寫入檔案

管道主要用來實作同一個虛拟機中的兩個線程進行交流。是以,一個管道既可以作為資料源媒介也可作為目标媒介。

需要注意的是java中的管道和unix/linux中的管道含義并不一樣,在unix/linux中管道可以作為兩個位于不同空間程序通信的媒介,而在java中,管道隻能為同一個jvm程序中的不同線程進行通信。和管道相關的io類為:pipedinputstream和pipedoutputstream,下面我們來看一個例子:

例9,讀寫管道

關于java io面向網絡媒介的操作即java 網絡程式設計,其核心是socket,同磁盤操作一樣,java網絡程式設計對應着兩套api,即java io和java nio,關于這部分我會準備專門的文章進行介紹。

bufferedinputstream顧名思義,就是在對流進行寫入時提供一個buffer來提高io效率。在進行磁盤或網絡io時,原始的inputstream對資料讀取的過程都是一個位元組一個位元組操作的,而bufferedinputstream在其内部提供了一個buffer,在讀資料時,會一次讀取一大塊資料到buffer中,這樣比單位元組的操作效率要高的多,特别是程序磁盤io和對大量資料進行讀寫的時候。

使用bufferedinputstream十分簡單,隻要把普通的輸入流和bufferedinputstream組合到一起即可。我們把上面的例2改造成用bufferedinputstream進行讀檔案,請看下面例子:

例10 ,用緩沖流讀檔案

關于如何設定buffer的大小,我們應根據我們的硬體狀況來确定。對于磁盤io來說,如果硬碟每次讀取4kb大小的檔案塊,那麼我們最好設定成這個大小的整數倍。因為磁盤對于順序讀的效率是特别高的,是以如果buffer再設定的大寫可能會帶來更好的效率,比如設定成4*4kb或8*4kb。

還需要注意一點的就是磁盤本身就會有緩存,在這種情況下,bufferedinputstream會一次讀取磁盤緩存大小的資料,而不是分多次的去讀。是以要想得到一個最優的buffer值,我們必須得知道磁盤每次讀的塊大小和其緩存大小,然後根據多次試驗的結果來得到最佳的buffer大小。

bufferedoutputstream的情況和bufferedinputstream一緻,在這裡就不多做描述了。

bufferedreader、bufferedwriter 的作用基本和bufferedinputstream、bufferedoutputstream一緻,具體用法和原理都差不多 ,隻不過一個是面向字元流一個是面向位元組流。同樣,我們将改造字元流中的例4,給其加上buffer功能,看例子: