?? 0116.htm
字號:
InputStream,沒有可選的緩沖區大小/本身并不能提供一個接口,只是發出使用緩沖區的要求。要求同一個接口對象連接到一起<br>
LineNumberInputStream 跟蹤輸入流中的行號;可調用getLineNumber()以及setLineNumber(int)
只是添加對數據行編號的能力,所以可能需要同一個真正的接口對象連接<br>
PushbackInputStream
有一個字節的后推緩沖區,以便后推讀入的上一個字符 InputStream/通常由編譯器在掃描器中使用,因為Java編譯器需要它。一般不在自己的代碼中使用<br>
<br>
10.2.2 通過FilterOutputStream向OutputStream里寫入數據<br>
與DataInputStream對應的是DataOutputStream,后者對各個基本數據類型以及String對象進行格式化,并將其置入一個數據“流”中,以便任何機器上的DataInputStream都能正常地讀取它們。所有方法都以“wirte”開頭,例如writeByte(),writeFloat()等等。<br>
若想進行一些真正的格式化輸出,比如輸出到控制臺,請使用PrintStream。利用它可以打印出所有基本數據類型以及String對象,并可采用一種易于查看的格式。這與DataOutputStream正好相反,后者的目標是將那些數據置入一個數據流中,以便DataInputStream能夠方便地重新構造它們。System.out靜態對象是一個PrintStream。<br>
PrintStream內兩個重要的方法是print()和println()。它們已進行了覆蓋處理,可打印出所有數據類型。print()和println()之間的差異是后者在操作完畢后會自動添加一個新行。<br>
BufferedOutputStream屬于一種“修改器”,用于指示數據流使用緩沖技術,使自己不必每次都向流內物理性地寫入數據。通常都應將它應用于文件處理和控制器IO。<br>
<br>
表10.4 FilterOutputStream的類型<br>
<br>
類 功能 構建器參數/如何使用<br>
<br>
DataOutputStream 與DataInputStream配合使用,以便采用方便的形式將基本數據類型(int,char,long等)寫入一個數據流
OutputStream/包含了完整接口,以便我們寫入基本數據類型<br>
PrintStream 用于產生格式化輸出。DataOutputStream控制的是數據的“存儲”,而PrintStream控制的是“顯示”
OutputStream,可選一個布爾參數,指示緩沖區是否與每個新行一同刷新/對于自己的OutputStream對象,應該用“final”將其封閉在內。可能經常都要用到它<br>
BufferedOutputStream
用它避免每次發出數據的時候都要進行物理性的寫入,要求它“請先在緩沖區里找”。可調用flush(),對緩沖區進行刷新
OutputStream,可選緩沖區大小/本身并不能提供一個接口,只是發出使用緩沖區的要求。需要同一個接口對象連接到一起<br>
<br>
10.3 本身的缺陷:RandomAccessFile<br>
RandomAccessFile用于包含了已知長度記錄的文件,以便我們能用seek()從一條記錄移至另一條;然后讀取或修改那些記錄。各記錄的長度并不一定相同;只要知道它們有多大以及置于文件何處即可。<br>
首先,我們有點難以相信RandomAccessFile不屬于InputStream或者OutputStream分層結構的一部分。除了恰巧實現了DataInput以及DataOutput(這兩者亦由DataInputStream和DataOutputStream實現)接口之外,它們與那些分層結構并無什么關系。它甚至沒有用到現有InputStream或OutputStream類的功能——采用的是一個完全不相干的類。該類屬于全新的設計,含有自己的全部(大多數為固有)方法。之所以要這樣做,是因為RandomAccessFile擁有與其他IO類型完全不同的行為,因為我們可在一個文件里向前或向后移動。不管在哪種情況下,它都是獨立運作的,作為Object的一個“直接繼承人”使用。<br>
從根本上說,RandomAccessFile類似DataInputStream和DataOutputStream的聯合使用。其中,getFilePointer()用于了解當前在文件的什么地方,seek()用于移至文件內的一個新地點,而length()用于判斷文件的最大長度。此外,構建器要求使用另一個自變量(與C的fopen()完全一樣),指出自己只是隨機讀("r"),還是讀寫兼施("rw")。這里沒有提供對“只寫文件”的支持。也就是說,假如是從DataInputStream繼承的,那么RandomAccessFile也有可能能很好地工作。<br>
還有更難對付的。很容易想象我們有時要在其他類型的數據流中搜索,比如一個ByteArrayInputStream,但搜索方法只有RandomAccessFile才會提供。而后者只能針對文件才能操作,不能針對數據流操作。此時,BufferedInputStream確實允許我們標記一個位置(使用mark(),它的值容納于單個內部變量中),并用reset()重設那個位置。但這些做法都存在限制,并不是特別有用。<br>
<br>
10.4 File類<br>
File類有一個欺騙性的名字——通常會認為它對付的是一個文件,但實情并非如此。它既代表一個特定文件的名字,也代表目錄內一系列文件的名字。若代表一個文件集,便可用list()方法查詢這個集,返回的是一個字串數組。之所以要返回一個數組,而非某個靈活的集合類,是因為元素的數量是固定的。而且若想得到一個不同的目錄列表,只需創建一個不同的File對象即可。事實上,“FilePath”(文件路徑)似乎是一個更好的名字。本節將向大家完整地例示如何使用這個類,其中包括相關的FilenameFilter(文件名過濾器)接口。<br>
<br>
10.4.1 目錄列表器<br>
現在假設我們想觀看一個目錄列表。可用兩種方式列出File對象。若在不含自變量(參數)的情況下調用list(),會獲得File對象包含的一個完整列表。然而,若想對這個列表進行某些限制,就需要使用一個“目錄過濾器”,該類的作用是指出應如何選擇File對象來完成顯示。<br>
下面是用于這個例子的代碼(或在執行該程序時遇到困難,請參考第3章3.1.2小節“賦值”):<br>
<br>
449-450頁程序<br>
<br>
DirFilter類“實現”了interface FilenameFilter(關于接口的問題,已在第7章進行了詳述)。下面讓我們看看FilenameFilter接口有多么簡單:<br>
<br>
public interface FilenameFilter {<br>
boolean accept(文件目錄, 字串名);<br>
}<br>
<br>
它指出這種類型的所有對象都提供了一個名為accept()的方法。之所以要創建這樣的一個類,背后的全部原因就是把accept()方法提供給list()方法,使list()能夠“回調”accept(),從而判斷應將哪些文件名包括到列表中。因此,通常將這種技術稱為“回調”,有時也稱為“算子”(也就是說,DirFilter是一個算子,因為它唯一的作用就是容納一個方法)。由于list()采用一個FilenameFilter對象作為自己的自變量使用,所以我們能傳遞實現了FilenameFilter的任何類的一個對象,用它決定(甚至在運行期)list()方法的行為方式。回調的目的是在代碼的行為上提供更大的靈活性。<br>
通過DirFilter,我們看出盡管一個“接口”只包含了一系列方法,但并不局限于只能寫那些方法(但是,至少必須提供一個接口內所有方法的定義。在這種情況下,DirFilter構建器也會創建)。<br>
accept()方法必須接納一個File對象,用它指示用于尋找一個特定文件的目錄;并接納一個String,其中包含了要尋找之文件的名字。可決定使用或忽略這兩個參數之一,但有時至少要使用文件名。記住list()方法準備為目錄對象中的每個文件名調用accept(),核實哪個應包含在內——具體由accept()返回的“布爾”結果決定。<br>
為確定我們操作的只是文件名,其中沒有包含路徑信息,必須采用String對象,并在它的外部創建一個File對象。然后調用getName(),它的作用是去除所有路徑信息(采用與平臺無關的方式)。隨后,accept()用String類的indexOf()方法檢查文件名內部是否存在搜索字串"afn"。若在字串內找到afn,那么返回值就是afn的起點索引;但假如沒有找到,返回值就是-1。注意這只是一個簡單的字串搜索例子,未使用常見的表達式“通配符”方案,比如"fo?.b?r*";這種方案更難實現。<br>
list()方法返回的是一個數組。可查詢這個數組的長度,然后在其中遍歷,選定數組元素。與C和C++的類似行為相比,這種于方法內外方便游歷數組的行為無疑是一個顯著的進步。<br>
<br>
1. 匿名內部類<br>
下例用一個匿名內部類(已在第7章講述)來重寫顯得非常理想。首先創建了一個filter()方法,它返回指向FilenameFilter的一個句柄:<br>
<br>
451-452頁程序<br>
<br>
注意filter()的自變量必須是final。這一點是匿名內部類要求的,使其能使用來自本身作用域以外的一個對象。<br>
之所以認為這樣做更好,是由于FilenameFilter類現在同DirList2緊密地結合在一起。然而,我們可采取進一步的操作,將匿名內部類定義成list()的一個參數,使其顯得更加精簡。如下所示:<br>
<br>
452頁下程序<br>
<br>
main()現在的自變量是final,因為匿名內部類直接使用args[0]。<br>
這展示了如何利用匿名內部類快速創建精簡的類,以便解決一些復雜的問題。由于Java中的所有東西都與類有關,所以它無疑是一種相當有用的編碼技術。它的一個好處是將特定的問題隔離在一個地方統一解決。但在另一方面,這樣生成的代碼不是十分容易閱讀,所以使用時必須慎重。<br>
<br>
2. 順序目錄列表<br>
經常都需要文件名以排好序的方式提供。由于Java 1.0和Java 1.1都沒有提供對排序的支持(從Java
1.2開始提供),所以必須用第8章創建的SortVector將這一能力直接加入自己的程序。就象下面這樣:<br>
<br>
453-454頁程序<br>
<br>
這里進行了另外少許改進。不再是將path(路徑)和list(列表)創建為main()的本地變量,它們變成了類的成員,使它們的值能在對象“生存”期間方便地訪問。事實上,main()現在只是對類進行測試的一種方式。大家可以看到,一旦列表創建完畢,類的構建器就會自動開始對列表進行排序。<br>
這種排序不要求區分大小寫,所以最終不會得到一組全部單詞都以大寫字母開頭的列表,跟著是全部以小寫字母開頭的列表。然而,我們注意到在以相同字母開頭的一組文件名中,大寫字母是排在前面的——這對標準的排序來說仍是一種不合格的行為。Java
1.2已成功解決了這個問題。<br>
<br>
10.4.2 檢查與創建目錄<br>
File類并不僅僅是對現有目錄路徑、文件或者文件組的一個表示。亦可用一個File對象新建一個目錄,甚至創建一個完整的目錄路徑——假如它尚不存在的話。亦可用它了解文件的屬性(長度、上一次修改日期、讀/寫屬性等),檢查一個File對象到底代表一個文件還是一個目錄,以及刪除一個文件等等。下列程序完整展示了如何運用File類剩下的這些方法:<br>
<br>
454-456頁程序<br>
<br>
在fileData()中,可看到應用了各種文件調查方法來顯示與文件或目錄路徑有關的信息。<br>
main()應用的第一個方法是renameTo(),利用它可以重命名(或移動)一個文件至一個全新的路徑(該路徑由參數決定),它屬于另一個File對象。這也適用于任何長度的目錄。<br>
若試驗上述程序,就可發現自己能制作任意復雜程度的一個目錄路徑,因為mkdirs()會幫我們完成所有工作。在Java
1.0中,-d標志報告目錄雖然已被刪除,但它依然存在;但在Java 1.1中,目錄會被實際刪除。<br>
<br>
10.5 IO流的典型應用<br>
盡管庫內存在大量IO流類,可通過多種不同的方式組合到一起,但實際上只有幾種方式才會經常用到。然而,必須小心在意才能得到正確的組合。下面這個相當長的例子展示了典型IO配置的創建與使用,可在寫自己的代碼時將其作為一個參考使用。注意每個配置都以一個注釋形式的編號起頭,并提供了適當的解釋信息。<br>
<br>
457-459頁程序<br>
<br>
10.5.1 輸入流<br>
當然,我們經常想做的一件事情是將格式化的輸出打印到控制臺,但那已在第5章創建的com.bruceeckel.tools中得到了簡化。<br>
第1到第4部分演示了輸入流的創建與使用(盡管第4部分展示了將輸出流作為一個測試工具的簡單應用)。<br>
<br>
1. 緩沖的輸入文件<br>
為打開一個文件以便輸入,需要使用一個FileInputStream,同時將一個String或File對象作為文件名使用。為提高速度,最好先對文件進行緩沖處理,從而獲得用于一個BufferedInputStream的構建器的結果句柄。為了以格式化的形式讀取輸入數據,我們將那個結果句柄賦給用于一個DataInputStream的構建器。DataInputStream是我們的最終(final)對象,并是我們進行讀取操作的接口。<br>
在這個例子中,只用到了readLine()方法,但理所當然任何DataInputStream方法都可以采用。一旦抵達文件末尾,readLine()就會返回一個null(空),以便中止并退出while循環。<br>
“String s2”用于聚集完整的文件內容(包括必須添加的新行,因為readLine()去除了那些行)。隨后,在本程序的后面部分中使用s2。最后,我們調用close(),用它關閉文件。從技術上說,會在運行finalize()時調用close()。而且我們希望一旦程序退出,就發生這種情況(無論是否進行垃圾收集)。然而,Java
1.0有一個非常突出的錯誤(Bug),造成這種情況不會發生。在Java 1.1中,必須明確調用System.runFinalizersOnExit(true),用它保證會為系統中的每個對象調用finalize()。然而,最安全的方法還是為文件明確調用close()。<br>
<br>
2. 從內存輸入<br>
這一部分采用已經包含了完整文件內容的String s2,并用它創建一個StringBufferInputStream(字串緩沖輸入流)——作為構建器的參數,要求使用一個String,而非一個StringBuffer)。隨后,我們用read()依次讀取每個字符,并將其發送至控制臺。注意read()將下一個字節返回為int,所以必須將其造型為一個char,以便正確地打印。<br>
<br>
3. 格式化內存輸入<br>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -