?? 0116.htm
字號(hào):
StringBufferInputStream的接口是有限的,所以通常需要將其封裝到一個(gè)DataInputStream內(nèi),從而增強(qiáng)它的能力。然而,若選擇用readByte()每次讀出一個(gè)字符,那么所有值都是有效的,所以不可再用返回值來偵測何時(shí)結(jié)束輸入。相反,可用available()方法判斷有多少字符可用。下面這個(gè)例子展示了如何從文件中一次讀出一個(gè)字符:<br>
<br>
461頁程序<br>
<br>
注意取決于當(dāng)前從什么媒體讀入,avaiable()的工作方式也是有所區(qū)別的。它在字面上意味著“可以不受阻塞讀取的字節(jié)數(shù)量”。對(duì)一個(gè)文件來說,它意味著整個(gè)文件。但對(duì)一個(gè)不同種類的數(shù)據(jù)流來說,它卻可能有不同的含義。因此在使用時(shí)應(yīng)考慮周全。<br>
為了在這樣的情況下偵測輸入的結(jié)束,也可以通過捕獲一個(gè)違例來實(shí)現(xiàn)。然而,若真的用違例來控制數(shù)據(jù)流,卻顯得有些大材小用。<br>
<br>
4. 行的編號(hào)與文件輸出<br>
這個(gè)例子展示了如何LineNumberInputStream來跟蹤輸入行的編號(hào)。在這里,不可簡單地將所有構(gòu)建器都組合起來,因?yàn)楸仨毐3諰ineNumberInputStream的一個(gè)句柄(注意這并非一種繼承環(huán)境,所以不能簡單地將in4造型到一個(gè)LineNumberInputStream)。因此,li容納了指向LineNumberInputStream的句柄,然后在它的基礎(chǔ)上創(chuàng)建一個(gè)DataInputStream,以便讀入數(shù)據(jù)。<br>
這個(gè)例子也展示了如何將格式化數(shù)據(jù)寫入一個(gè)文件。首先創(chuàng)建了一個(gè)FileOutputStream,用它同一個(gè)文件連接。考慮到效率方面的原因,它生成了一個(gè)BufferedOutputStream。這幾乎肯定是我們一般的做法,但卻必須明確地這樣做。隨后為了進(jìn)行格式化,它轉(zhuǎn)換成一個(gè)PrintStream。用這種方式創(chuàng)建的數(shù)據(jù)文件可作為一個(gè)原始的文本文件讀取。<br>
標(biāo)志DataInputStream何時(shí)結(jié)束的一個(gè)方法是readLine()。一旦沒有更多的字串可以讀取,它就會(huì)返回null。每個(gè)行都會(huì)伴隨自己的行號(hào)打印到文件里。該行號(hào)可通過li查詢。<br>
可看到用于out1的、一個(gè)明確指定的close()。若程序準(zhǔn)備掉轉(zhuǎn)頭來,并再次讀取相同的文件,這種做法就顯得相當(dāng)有用。然而,該程序直到結(jié)束也沒有檢查文件IODemo.txt。正如以前指出的那樣,如果不為自己的所有輸出文件調(diào)用close(),就可能發(fā)現(xiàn)緩沖區(qū)不會(huì)得到刷新,造成它們不完整。。<br>
<br>
10.5.2 輸出流<br>
兩類主要的輸出流是按它們寫入數(shù)據(jù)的方式劃分的:一種按人的習(xí)慣寫入,另一種為了以后由一個(gè)DataInputStream而寫入。RandomAccessFile是獨(dú)立的,盡管它的數(shù)據(jù)格式兼容于DataInputStream和DataOutputStream。<br>
<br>
5. 保存與恢復(fù)數(shù)據(jù)<br>
PrintStream能格式化數(shù)據(jù),使其能按我們的習(xí)慣閱讀。但為了輸出數(shù)據(jù),以便由另一個(gè)數(shù)據(jù)流恢復(fù),則需用一個(gè)DataOutputStream寫入數(shù)據(jù),并用一個(gè)DataInputStream恢復(fù)(獲取)數(shù)據(jù)。當(dāng)然,這些數(shù)據(jù)流可以是任何東西,但這里采用的是一個(gè)文件,并進(jìn)行了緩沖處理,以加快讀寫速度。<br>
注意字串是用writeBytes()寫入的,而非writeChars()。若使用后者,寫入的就是16位Unicode字符。由于DataInputStream中沒有補(bǔ)充的“readChars”方法,所以不得不用readChar()每次取出一個(gè)字符。所以對(duì)ASCII來說,更方便的做法是將字符作為字節(jié)寫入,在后面跟隨一個(gè)新行;然后再用readLine()將字符當(dāng)作普通的ASCII行讀回。<br>
writeDouble()將double數(shù)字保存到數(shù)據(jù)流中,并用補(bǔ)充的readDouble()恢復(fù)它。但為了保證任何讀方法能夠正常工作,必須知道數(shù)據(jù)項(xiàng)在流中的準(zhǔn)確位置,因?yàn)榧扔锌赡軐⒈4娴膁ouble數(shù)據(jù)作為一個(gè)簡單的字節(jié)序列讀入,也有可能作為char或其他格式讀入。所以必須要么為文件中的數(shù)據(jù)采用固定的格式,要么將額外的信息保存到文件中,以便正確判斷數(shù)據(jù)的存放位置。<br>
<br>
6. 讀寫隨機(jī)訪問文件<br>
正如早先指出的那樣,RandomAccessFile與IO層次結(jié)構(gòu)的剩余部分幾乎是完全隔離的,盡管它也實(shí)現(xiàn)了DataInput和DataOutput接口。所以不可將其與InputStream及OutputStream子類的任何部分關(guān)聯(lián)起來。盡管也許能將一個(gè)ByteArrayInputStream當(dāng)作一個(gè)隨機(jī)訪問元素對(duì)待,但只能用RandomAccessFile打開一個(gè)文件。必須假定RandomAccessFile已得到了正確的緩沖,因?yàn)槲覀儾荒茏孕羞x擇。<br>
可以自行選擇的是第二個(gè)構(gòu)建器參數(shù):可決定以“只讀”(r)方式或“讀寫”(rw)方式打開一個(gè)RandomAccessFile文件。<br>
使用RandomAccessFile的時(shí)候,類似于組合使用DataInputStream和DataOutputStream(因?yàn)樗鼘?shí)現(xiàn)了等同的接口)。除此以外,還可看到程序中使用了seek(),以便在文件中到處移動(dòng),對(duì)某個(gè)值作出修改。<br>
<br>
10.5.3 快捷文件處理<br>
由于以前采用的一些典型形式都涉及到文件處理,所以大家也許會(huì)懷疑為什么要進(jìn)行那么多的代碼輸入——這正是裝飾器方案一個(gè)缺點(diǎn)。本部分將向大家展示如何創(chuàng)建和使用典型文件讀取和寫入配置的快捷版本。這些快捷版本均置入packagecom.bruceeckel.tools中(自第5章開始創(chuàng)建)。為了將每個(gè)類都添加到庫內(nèi),只需將其置入適當(dāng)?shù)哪夸洠⑻砑訉?duì)應(yīng)的package語句即可。<br>
<br>
7. 快速文件輸入<br>
若想創(chuàng)建一個(gè)對(duì)象,用它從一個(gè)緩沖的DataInputStream中讀取一個(gè)文件,可將這個(gè)過程封裝到一個(gè)名為InFile的類內(nèi)。如下所示:<br>
<br>
463-464頁程序<br>
<br>
無論構(gòu)建器的String版本還是File版本都包括在內(nèi),用于共同創(chuàng)建一個(gè)FileInputStream。<br>
就象這個(gè)例子展示的那樣,現(xiàn)在可以有效減少創(chuàng)建文件時(shí)由于重復(fù)強(qiáng)調(diào)造成的問題。<br>
<br>
8. 快速輸出格式化文件<br>
亦可用同類型的方法創(chuàng)建一個(gè)PrintStream,令其寫入一個(gè)緩沖文件。下面是對(duì)com.bruceeckel.tools的擴(kuò)展:<br>
<br>
464-465頁程序<br>
<br>
注意構(gòu)建器不可能捕獲一個(gè)由基礎(chǔ)類構(gòu)建器“擲”出的違例。<br>
<br>
9. 快速輸出數(shù)據(jù)文件<br>
最后,利用類似的快捷方式可創(chuàng)建一個(gè)緩沖輸出文件,用它保存數(shù)據(jù)(與由人觀看的數(shù)據(jù)格式相反):<br>
<br>
465頁程序<br>
<br>
非常奇怪的是(也非常不幸),Java庫的設(shè)計(jì)者居然沒想到將這些便利措施直接作為他們的一部分標(biāo)準(zhǔn)提供。<br>
<br>
10.5.4 從標(biāo)準(zhǔn)輸入中讀取數(shù)據(jù)<br>
以Unix首先倡導(dǎo)的“標(biāo)準(zhǔn)輸入”、“標(biāo)準(zhǔn)輸出”以及“標(biāo)準(zhǔn)錯(cuò)誤輸出”概念為基礎(chǔ),Java提供了相應(yīng)的System.in,System.out以及System.err。貫這一整本書,大家都會(huì)接觸到如何用System.out進(jìn)行標(biāo)準(zhǔn)輸出,它已預(yù)封裝成一個(gè)PrintStream對(duì)象。System.err同樣是一個(gè)PrintStream,但System.in是一個(gè)原始的InputStream,未進(jìn)行任何封裝處理。這意味著盡管能直接使用System.out和System.err,但必須事先封裝System.in,否則不能從中讀取數(shù)據(jù)。<br>
典型情況下,我們希望用readLine()每次讀取一行輸入信息,所以需要將System.in封裝到一個(gè)DataInputStream中。這是Java
1.0進(jìn)行行輸入時(shí)采取的“老”辦法。在本章稍后,大家還會(huì)看到Java
1.1的解決方案。下面是個(gè)簡單的例子,作用是回應(yīng)我們鍵入的每一行內(nèi)容:<br>
<br>
466頁程序<br>
<br>
之所以要使用try塊,是由于readLine()可能“擲”出一個(gè)IOException。注意同其他大多數(shù)流一樣,也應(yīng)對(duì)System.in進(jìn)行緩沖。<br>
由于在每個(gè)程序中都要將System.in封裝到一個(gè)DataInputStream內(nèi),所以顯得有點(diǎn)不方便。但采用這種設(shè)計(jì)方案,可以獲得最大的靈活性。<br>
<br>
10.5.5 管道數(shù)據(jù)流<br>
本章已簡要介紹了PipedInputStream(管道輸入流)和PipedOutputStream(管道輸出流)。盡管描述不十分詳細(xì),但并不是說它們作用不大。然而,只有在掌握了多線程處理的概念后,才可真正體會(huì)它們的價(jià)值所在。原因很簡單,因?yàn)楣艿阑臄?shù)據(jù)流就是用于線程之間的通信。這方面的問題將在第14章用一個(gè)示例說明。<br>
<br>
10.6 StreamTokenizer<br>
盡管StreamTokenizer并不是從InputStream或OutputStream衍生的,但它只隨同InputStream工作,所以十分恰當(dāng)?shù)匕ㄔ趲斓腎O部分中。<br>
StreamTokenizer類用于將任何InputStream分割為一系列“記號(hào)”(Token)。這些記號(hào)實(shí)際是一些斷續(xù)的文本塊,中間用我們選擇的任何東西分隔。例如,我們的記號(hào)可以是單詞,中間用空白(空格)以及標(biāo)點(diǎn)符號(hào)分隔。<br>
下面是一個(gè)簡單的程序,用于計(jì)算各個(gè)單詞在文本文件中重復(fù)出現(xiàn)的次數(shù):<br>
<br>
467-469頁程序<br>
<br>
最好將結(jié)果按排序格式輸出,但由于Java 1.0和Java 1.1都沒有提供任何排序方法,所以必須由自己動(dòng)手。這個(gè)目標(biāo)可用一個(gè)StrSortVector方便地達(dá)成(創(chuàng)建于第8章,屬于那一章創(chuàng)建的軟件包的一部分。記住本書所有子目錄的起始目錄都必須位于類路徑中,否則程序?qū)⒉荒苷_地編譯)。<br>
為打開文件,使用了一個(gè)FileInputStream。而且為了將文件轉(zhuǎn)換成單詞,從FileInputStream中創(chuàng)建了一個(gè)StreamTokenizer。在StreamTokenizer中,存在一個(gè)默認(rèn)的分隔符列表,我們可用一系列方法加入更多的分隔符。在這里,我們用ordinaryChar()指出“該字符沒有特別重要的意義”,所以解析器不會(huì)把它當(dāng)作自己創(chuàng)建的任何單詞的一部分。例如,st.ordinaryChar('.')表示小數(shù)點(diǎn)不會(huì)成為解析出來的單詞的一部分。在與Java配套提供的聯(lián)機(jī)文檔中,可以找到更多的相關(guān)信息。<br>
在countWords()中,每次從數(shù)據(jù)流中取出一個(gè)記號(hào),而ttype信息的作用是判斷對(duì)每個(gè)記號(hào)采取什么操作——因?yàn)橛浱?hào)可能代表一個(gè)行尾、一個(gè)數(shù)字、一個(gè)字串或者一個(gè)字符。<br>
找到一個(gè)記號(hào)后,會(huì)查詢Hashtable counts,核實(shí)其中是否已經(jīng)以“鍵”(Key)的形式包含了一個(gè)記號(hào)。若答案是肯定的,對(duì)應(yīng)的Counter(計(jì)數(shù)器)對(duì)象就會(huì)增值,指出已找到該單詞的另一個(gè)實(shí)例。若答案為否,則新建一個(gè)Counter——因?yàn)镃ounter構(gòu)建器會(huì)將它的值初始化為1,正是我們計(jì)算單詞數(shù)量時(shí)的要求。<br>
SortedWordCount并不屬于Hashtable(散列表)的一種類型,所以它不會(huì)繼承。它執(zhí)行的一種特定類型的操作,所以盡管keys()和values()方法都必須重新揭示出來,但仍不表示應(yīng)使用那個(gè)繼承,因?yàn)榇罅縃ashtable方法在這里都是不適當(dāng)?shù)摹3艘酝猓瑢?duì)于另一些方法來說(比如getCounter()——用于獲得一個(gè)特定字串的計(jì)數(shù)器;又如sortedKeys()——用于產(chǎn)生一個(gè)枚舉),它們最終都改變了SortedWordCount接口的形式。<br>
在main()內(nèi),我們用SortedWordCount打開和計(jì)算文件中的單詞數(shù)量——總共只用了兩行代碼。隨后,我們?yōu)橐粋€(gè)排好序的鍵(單詞)列表提取出一個(gè)枚舉。并用它獲得每個(gè)鍵以及相關(guān)的Count(計(jì)數(shù))。注意必須調(diào)用cleanup(),否則文件不能正常關(guān)閉。<br>
采用了StreamTokenizer的第二個(gè)例子將在第17章提供。<br>
<br>
10.6.1 StringTokenizer<br>
盡管并不必要IO庫的一部分,但StringTokenizer提供了與StreamTokenizer極相似的功能,所以在這里一并講述。<br>
StringTokenizer的作用是每次返回字串內(nèi)的一個(gè)記號(hào)。這些記號(hào)是一些由制表站、空格以及新行分隔的連續(xù)字符。因此,字串“Where
is my cat?”的記號(hào)分別是“Where”、“is”、“my”和“cat?”。與StreamTokenizer類似,我們可以指示StringTokenizer按照我們的愿望分割輸入。但對(duì)于StringTokenizer,卻需要向構(gòu)建器傳遞另一個(gè)參數(shù),即我們想使用的分隔字串。通常,如果想進(jìn)行更復(fù)雜的操作,應(yīng)使用StreamTokenizer。<br>
可用nextToken()向StringTokenizer對(duì)象請(qǐng)求字串內(nèi)的下一個(gè)記號(hào)。該方法要么返回一個(gè)記號(hào),要么返回一個(gè)空字串(表示沒有記號(hào)剩下)。<br>
作為一個(gè)例子,下述程序?qū)?zhí)行一個(gè)有限的句法分析,查詢鍵短語序列,了解句子暗示的是快樂亦或悲傷的含義。<br>
<br>
471-472頁程序<br>
<br>
對(duì)于準(zhǔn)備分析的每個(gè)字串,我們進(jìn)入一個(gè)while循環(huán),并將記號(hào)從那個(gè)字串中取出。請(qǐng)注意第一個(gè)if語句,假如記號(hào)既不是“I”,也不是“Are”,就會(huì)執(zhí)行continue(返回循環(huán)起點(diǎn),再一次開始)。這意味著除非發(fā)現(xiàn)一個(gè)“I”或者“Are”,才會(huì)真正得到記號(hào)。大家可能想用==代替equals()方法,但那樣做會(huì)出現(xiàn)不正常的表現(xiàn),因?yàn)?=比較的是句柄值,而equals()比較的是內(nèi)容。<br>
analyze()方法剩余部分的邏輯是搜索“I am sad”(我很憂傷、“I am
nothappy”(我不快樂)或者“Are you sad?”(你悲傷嗎?)這樣的句法格式。若沒有break語句,這方面的代碼甚至可能更加散亂。大家應(yīng)注意對(duì)一個(gè)典型的解析器來說,通常都有這些記號(hào)的一個(gè)表格,并能在讀取新記號(hào)的時(shí)候用一小段代碼在表格內(nèi)移動(dòng)。<br>
無論如何,只應(yīng)將StringTokenizer看作StreamTokenizer一種簡單而且特殊的簡化形式。然而,如果有一個(gè)字串需要進(jìn)行記號(hào)處理,而且StringTokenizer的功能實(shí)在有限,那么應(yīng)該做的全部事情就是用StringBufferInputStream將其轉(zhuǎn)換到一個(gè)數(shù)據(jù)流里,再用它創(chuàng)建一個(gè)功能更強(qiáng)大的StreamTokenizer。<br>
<br>
10.7 Java 1.1的IO流<br>
到這個(gè)時(shí)候,大家或許會(huì)陷入一種困境之中,懷疑是否存在IO流的另一種設(shè)計(jì)方案,并可能要求更大的代碼量。還有人能提出一種更古怪的設(shè)計(jì)嗎?事實(shí)上,Java
1.1對(duì)IO流庫進(jìn)行了一些重大的改進(jìn)。看到Reader和Writer類時(shí),大多數(shù)人的第一個(gè)印象(就象我一樣)就是它們用來替換原來的InputStream和OutputStream類。但實(shí)情并非如此。盡管不建議使用原始數(shù)據(jù)流庫的某些功能(如使用它們,會(huì)從編譯器收到一條警告消息),但原來的數(shù)據(jù)流依然得到了保留,以便維持向后兼容,而且:<br>
(1) 在老式層次結(jié)構(gòu)里加入了新類,所以Sun公司明顯不會(huì)放棄老式數(shù)據(jù)流。<br>
(2)
在許多情況下,我們需要與新結(jié)構(gòu)中的類聯(lián)合使用老結(jié)構(gòu)中的類。為達(dá)到這個(gè)目的,需要使用一些“橋”類:InputStreamReader將一個(gè)InputStream轉(zhuǎn)換成Reader,OutputStreamWriter將一個(gè)OutputStream轉(zhuǎn)換成Writer。<br>
所以與原來的IO流庫相比,經(jīng)常都要對(duì)新IO流進(jìn)行層次更多的封裝。同樣地,這也屬于裝飾器方案的一個(gè)缺點(diǎn)——需要為額外的靈活性付出代價(jià)。<br>
之所以在Java 1.1里添加了Reader和Writer層次,最重要的原因便是國際化的需求。老式IO流層次結(jié)構(gòu)只支持8位字節(jié)流,不能很好地控制16位Unicode字符。由于Unicode主要面向的是國際化支持(Java內(nèi)含的char是16位的Unicode),所以添加了Reader和Writer層次,以提供對(duì)所有IO操作中的Unicode的支持。除此之外,新庫也對(duì)速度進(jìn)行了優(yōu)化,可比舊庫更快地運(yùn)行。<br>
與本書其他地方一樣,我會(huì)試著提供對(duì)類的一個(gè)概述,但假定你會(huì)利用聯(lián)機(jī)文檔搞定所有的細(xì)節(jié),比如方法的詳盡列表等。<br>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -