曾經看過很多關于java I/O的文章,可能由于作者中文邏輯沒有組織好,我到頭來還是一頭霧水。今晚稍看了一下原版英文I/O講解,突然就來一個豁然貫通,之後就整理了一下,希望可以給你帶來那麼一點微小的價值。
在java裡,按操作機關劃分為位元組流和字元流。流就是一種序列化的資料,可以簡單到一個位元組/字元或者一段文本,也可以是各種複雜的對象(如圖檔、音頻等)。而程式就是通過輸入流從某位置(硬碟或記憶體等)讀取一段資料進來,通過輸出流将一段資料輸出到某位置,每次流的操作都是一個基本的機關(位元組或字元)來進行的。如圖:
下面将從基本的資料類型開始,講解如何使用java的I/O流來處理各種的資料。在講解之前首先定義如下一個文本original.txt
Java I/O stream is easier than you think, if you can look through the blog to the end. And if permitted, please share it with others. Thanks for your reanding.
Demo:建立一個類BytesTest,将original.txt的内容複制到byteout.txt裡。代碼如下:
以上代碼是基于java7來寫的,try()子產品裡可以自動關閉各個實作了AutoCloseable的輸入輸出流(不關閉流可能導緻記憶體的洩露),java7以前可以這樣來寫:
附:1.在BytesTest裡,其時間主要用在輸入輸出的循環裡。同時每次操作都是一個資料機關——位元組,即複制完“J”之後,在複制“a”,接着就是“v”,如此類推直到其結束。
2.in.read()每次從輸入流中讀取一個位元組;如果達到檔案末尾就傳回-1.
盡管BytesTest能夠順利執行,可它并不完美。位元組流一般用在原始的I/O裡(即操作位元組資料的時候),而original.txt包含的确是一些字元資料,是以更好的處理方式應該是使用字元流。可為什麼要先談位元組流呢??因為其他流都是建立在位元組流之上的。
java是使用16-bits來存儲字元資料的,是以很多時候,在程式中使用字元流會比位元組流更加合适。
所有字元流都是Reader和Writer的子類。位元組流一樣,在字元流也有着操作檔案流的類:FileReader和FileWriter。
Demo:一樣是複制original.txt裡的内容,隻不過這次使用的是字元流,代碼如下:
看到CharactersTest之後,發現其與BytesTest非常相似,不同隻是用FileReader和FileWriter來代替了FileInputStream和FileOutputStream。同時,FileReader和FileWriter的操作機關是一個字元(16-bits),FileInputStream和FileOutputStream操作的機關是一個位元組(8-bits)
字元流通常也可以是位元組流的封裝。處理好位元組流與字元流之間的轉換,就可以讓封裝了位元組流的字元流來操縱實體的I/O。當中FileReader使用FileInputStream,FileWriter使用FileOutputStream。
InputStreamReader和OutputStreamWriter是位元組流轉換為字元流的橋梁。當找不到滿足需求的合适字元流類,可以選擇使用InputStreamReader和OutputStreamWriter來建立字元流類。典型的例子有:使用InputStreamReader和OutputStreamWriter來封裝socket類中的位元組流。
InputStreamReader,可以在構造器指定編碼的格式,如果不指定,則使用地層作業系統的預設編碼格式,如GBK等。FileReader 與 InputStreamReader 涉及編碼轉換 ( 指定編碼方式或者采用 os 預設編碼 ) ,可能在不同的平台上出現亂碼現象!而 FileInputStream 以二進制方式處理,不會出現亂碼現象 .
每次調用 InputStreamReader 中的一個 read() 方法都會導緻從底層輸入流讀取一個或多個位元組。要啟用從位元組到字元的有效轉換,可以提前從底層流讀取更多的位元組,使其超過滿足目前讀取操作所需的位元組。為了達到最高效率,可要考慮在 BufferedReader 内包裝 InputStreamReader。例如:
類似的,可以如此看待OutputStreamWriter:
在實際開發中,字元流操作的往往并不是單個字元,而是一個成塊的單元——行。多數的作業系統都支援”行“的操作,且行的終止符都為”\r\n”。
接着我們嘗試去改編CharactesTest,使其以“行”操作單元來讀取original.txt。為了完成這種操作,再次引入兩個新的類BufferedReader和PrintWriter。在後面的緩沖流中,我們再會詳談這兩個類,現在我們隻需要知道如何去操作這兩個類就行了。代碼如下:
通過代碼可以知道,BufferedReader.readLine()方法可以整行讀取original.txt的文本,同樣地可以使用PrintWriter.println()方法整行輸出,并且會自動在行末端加上行終止符(可能與輸入流的行終止符不一緻)。
在java裡,對于結構化文本的輸入輸出,除了上面談到的字元流和線性I/O,還有着其他多種方式,詳情可以檢視後面章節(Scaning和Formatting)。
在上面談到的各種I/O方式都是不帶緩沖的,意味着這種讀寫請求會直接與地層作業系統打交道。因為需要頻繁地觸發硬碟的通路,導緻程式性能嚴重降低,那又有什麼方式可以減少觸發硬碟的通路呢??為此,java提供裡緩沖流的操作。所謂的“緩沖流”,就是在存儲媒體(硬碟)與程式之間搭建一個或多個緩沖區/池,緩沖輸入流從緩沖區裡讀取資料,當緩沖區為空的時候才會再次調用本地輸入API将存儲媒體的資料輸入緩沖區;同樣的,緩沖輸出流将資料輸入到緩沖區,當緩沖區滿了的時候才會調用本地輸出API将緩沖區的資料輸入到存儲媒體。緩沖區就類似一個裝資料的“水桶”,隻有當裝滿的時候,才會倒向另外一個大桶(程式或存儲媒體)。
程式可以将非緩沖的輸入流轉換到有緩沖的輸入流,方法很簡單:将非緩沖流的對象作為緩沖流構造參數就行了。如下,将CharactersTest轉換為帶緩沖的I/O流:
java提供了四個将非緩沖流封裝成緩沖流的類:BufferedInputStream和BufferedOutputStream可以将位元組流封裝成緩沖流;相對應的有,BufferedReader和BufferedWriter将字元流封裝成緩沖流。
某些教程書也會把它叫做“刷空緩沖流”,意思都是指在緩沖區/池尚未滿的時候,就強制将緩沖區内的内容輸出。有部分緩沖流可以在構造函數裡指定是否自動重新整理緩沖流。當緩沖流可以自動重新整理的時候,某部分函數的使用(或事件的觸發)會引起緩沖區的自動重新整理。如可以自動重新整理的PrintWriter,當每次調用println()或format()的時候都會自動重新整理緩沖區。
當然了,可以手動進行緩沖區的重新整理,調用flush()方法就行了。每個輸出流對象都可以調用flush()方法,可隻當該輸出流是緩沖流的時候,flush()方法 才有效。
在寫代碼的時候,偶爾會遇到一些已經組織好的資料,而我們并不需要輸出全部文本内容,隻要求輸出指定的資料就可以了。為了完成這一功能,java提供了類API,Scanner API将資料分為一個個标記,讓你有選擇地輸出某部分資料;而格式化則可以将資料組織好的格式輸出。
一個可以使用正規表達式來解析基本類型和字元串的簡單文本掃描器。<code>Scanner</code> 使用分隔符模式将其輸入分解為标記,預設情況下該分隔符模式與空白比對。然後可以使用不同的 next 方法将得到的标記轉換為不同類型的值。
首先,了解一下Scanner如何将将資料分解成各種标記,看如下代碼:
Java
I/O
stream
is
easier
than
接着,看一下如何使用Scanner擷取指定的資料:(number.txt的内容如下:Testing Scanner: 10 + 3.0 = 13.0 is true or false ???)
其輸出如下:
String: Testing
String: Scanner:
int: 10
String: +
double: 3.0
String: =
double: 13.0
String: is
boolean: true
String: or
boolean: false
String: ???
可見,通過Scanner,加上恰當的邏輯可以輸出指定類型的資料。其實,Scanner還有着還有這一種更為常見的應用——擷取控制台的輸入:如下所示可從控制台讀取一個整數:
掃描器還可以使用不同于空白的分隔符。下面是從一個字元串讀取若幹項的例子:
輸出為:
1
2
red
blue
附:Scanner可以使用useDelimiter()另外設定分隔符(預設是空格符)。
格式化(Format)
在輸入輸出流裡,可以實作格式化的有兩個類PrintStream和PrintWriter。PrintStream用于格式化位元組流的輸出,PrintWriter用于格式化字元流的輸出。
tips:如果你需要格式化流的輸出,一般使用的都是PrintWriter。使用到PrintStream對象一般都是syste,out和system.err。
PrintStream和PrintWriter對象都有着各種write()方法,另外值得提一下的是,PrintStream和PrintWriter擁有着基本一緻的方法,分别用來輸出位元組流和字元流。這兩個類提供了兩類用于格式輸出的方法:
print & println 以一種正常的方式輸出各個變量
format 提供了多種控制輸出格式的字元,讓你可以使用指定格式來輸出輸出
print & println 方法
以一個Demo來說明如何使用這兩個方法:
format方法
使用format方法可以更好地控制輸出的格式,看一下FormatTest.java
一個程式通常會從指令行開始運作,同時通過指令行與使用者進行互動(如果沒有使用IDE開發,對這種情況就會有比較清晰的感受)。java提供了兩種與指令行互動的方式:通過标準流(Standard Streams)或者通過控制台(Console)。
Standard Streams 是多數作業系統都支援的一種功能。預設是從鍵盤讀取輸入和輸出到顯示器上。同時,Standard Streams 也支援檔案和程式之間的I/O,不過這些都是有指令行來編譯的,而不是通過程式編譯的。
java支援三種标準流:System.in , System.out , System.err。對着三種标準流,相信大家也知之甚多,就不在一一解釋了。值得一提的是,由于各種曆史原因,标準流一直都是位元組流,都是PrintStream的對象。不過了,System.out 和 System.err 利用了内部字元流來模仿實作了字元流的功能,System,in就還是位元組流,如果想要模仿字元流的輸入功能,就要使用InputStreamReader封裝System.in——
Console(控制台),提供了比标準流更多的功能。重要的是,Console可以有效地保護密碼的輸入,同時Console對象可以使用各種read()和write()方法來操縱字元流的輸入輸出。在程式使用Console對象之前,需要先調用System.console()方法來獲得Console對象。Console對象使用readPassword()方法來保護使用者密碼的輸入,readPassword的保護機制主要有兩方面:第一,将使用者輸入的密碼以圓點顯示在顯示器上;第二,readPassword傳回的是一個字元數組,是以當密碼不在使用時,可以将其覆寫并從記憶體移走(如果傳回的是String對象,可能會保留此對象的引用,存在洩密的可能。
使用PasswordDemo來示範一下如何使用Console對象的方法:
Data Stream可以實作基本資料類型和String值的二進制輸入輸出。所有的資料流都實作了DataInput或DataOutput接口。另外啦,我們會談一下用的比較的兩個類DataInputStream和DataOutputStream。DataStreamTest示範了如何輸出一份清單上的資料和如何讀取這些資料.清單如下:
<b>記錄</b>
<b>資料類型</b>
<b>描述</b>
<b>輸出方法</b>
<b>輸入方法</b>
<b>樣例值</b>
double
價格
DataOutputStream.writeDouble
DataInputStream.readDouble
99.99
int
數量
DataOutputStream.writeInt
DataInputStream.readInt
3
String
商品描述
DataOutputStream.writeUTF
DataInputStream.readUTF
"T-Shirt"
附:Data Stream需要捕捉EOFException,而不是像之前的字元流或位元組流一樣(達到檔案末尾時傳回-1)。DataInputStream和DataOutputStream的writeXxx和readXxx方法是對應一緻,在使用的時候,注意資料類型的比對。
除了基本資料類型和String的輸入輸出,java也支援對象的輸入輸出。對象流操作類有ObjectInputStream和ObjectOutputStream,這兩個類又分别實作了ObjectInput和ObjectOutput接口,同時ObjectInput和ObjectOutput是<code>DataInput</code> 和<code>DataOutput的子接口。</code>
ObjectInput 擴充 DataInput 接口以包含對象的讀操作。DataInput 包括基本類型的輸入方法;ObjectInput 擴充了該接口,以包含對象、數組和 String 的輸出方法。ObjectOutput 擴充 DataOutput 接口以包含對象的寫入操作。DataOutput 包括基本類型的輸出方法;ObjectOutput 擴充了該接口,以包含對象、數組和 String 的輸出方法。ObjectInputStream(對象輸入流)可讀取使用ObjectOutputStream寫入的原始資料和類型,與檔案輸入輸出流一起可以實作對象的持久性存儲。ObjectOutputStream(對象輸出流)可将Java的原始資料類型和圖形寫入輸出流,對象可以使用對象輸入流讀取,使用檔案可以實作對象的持久存儲。
ObjectStreamDemo:将日期對象和向量對象寫入檔案,然後從檔案中讀出并輸出到螢幕上,要求向量對象含有三個值“國文”、“數學”和“英語”,代碼如下:
本文轉自peiquan 51CTO部落格,原文連結:http://blog.51cto.com/peiquan/1286022