1、著作權所有 旗標出版股份有限公司1第第 16 章章資料輸入與輸出資料輸入與輸出2本章提要本章提要l16-1 甚麼是串流?l16-2 Java 串流類別架構l16-3 輸出、輸入資料l16-4 物件的讀寫l16-5 綜合演練3前言前言l在本章之前,我們已多次用 import java.io.*敘述匯入 Java 的 I/O(資料輸入與輸出)套件,並使用其中的 BufferedReader 類別的 readLine()方法從鍵盤讀取使用者輸入的資料,以及用 System.out.println()方法在螢幕上顯示訊息或輸出程式執行的結果。4前言前言l但 java.io 套件的功能可不僅止於此,舉凡
2、從電腦的螢幕、鍵盤等各種裝置輸出或輸入資料,或是讀寫電腦中的文字檔、二元檔(binary file),甚至是讀寫 ZIP 格式的壓縮檔,都可透過 java.io 套件中的類別來完成。本章就要來介紹 Java 的資料輸入與輸出架構,以及如何使用 java.io 套件的各項 I/O 類別。516-1 甚麼是串流?甚麼是串流?l為了簡化程式設計人員處理 I/O 的動作,不管讀取資料或寫入資料的來源/目的為何(檔案、網路、或記憶體等等),都是以串流(stream)的方式進行資料的讀取與寫入。而串流就是形容資料像一條河流一樣,將資料依序從資料來源中流出,或是流入目的地中。6甚麼是串流?甚麼是串流?7甚麼
3、是串流?甚麼是串流?l在 java.io 套件中,所有的資料輸出入類別都是以串流的方式來操作資料,不管讀取或寫入,都離不開以下三個基本動作:1.開啟串流(建構串流物件)2.從串流讀取資料、或將資料寫入串流3.關閉串流8甚麼是串流?甚麼是串流?l從程式的觀點,可供程式讀取的資料來源稱為輸入串流(input stream);而可用來寫入資料的則稱為輸出串流(output stream)。不管我們是從磁碟(檔案)、網路(URL)或其它來源或目的建立串流物件,讀寫的方式都相似,Java 已替我們將其間的不同隱藏起來,讓我們可以用一致的方式來操作串流,大幅簡化學習過程。916-2 Java 串流類別架構
4、串流類別架構l在 java.io 套件中,共有 4 組串流類別,這 4 組類別可分為兩大類:l以以 byte 為處理單位為處理單位的輸出入串流,又可稱之為位元串流(Byte Streams)l以以 char 為處理單位為處理單位的輸出入串流,又可稱之為字元串流(Character Streams)10位元串流位元串流l位元串流是以 8 位元的 byte 為單位進行資料的讀寫,位元串流有兩個最上層的抽像類別:InputStream(輸入)及 OutputStream(輸出)。所有的輸出入位元串流都是由這兩個類別衍生出來的,例如我們已用過很多次的 System.out,它是個 java.io.Pr
5、intStream 類別的物件,此類別是 FilterOutputStream 的子類別,而 FilterOutputStream 則是 OutputStream 的子類別。11位元串流位元串流l關於位元串流的主要類別,請參見以下的類別圖:12位元串流位元串流13位元串流位元串流l每種類別都適合於某類的讀取或寫入的動作,例如 ByteArrayInputStream 適用於讀取位元陣列;FileOutputStream 則適用於寫入檔案。l另外比較特別的是 ObjectIntputStream 和ObjectOutputStream,這兩個串流類別是特別為了讀寫我們自訂類別的物件而設計,其詳細
6、用法會在 16-4 節中介紹。14位元串流位元串流l這些串流類別的讀/寫方法都有個共通的特性,就是它們的原型宣告都註明 throws IOException,所以使用這些方法時,要記得用 try/catch 來執行,或是在您的方法宣告也加上 throws IOException 的註記,將例外拋給上層。15字元串流字元串流l字元串流是以 16 位元的 char 為單位進行資料的讀寫,字元串流同樣有兩個最上層的抽像類別 Reader、Writer,分別對應於位元串流的 InputStream、OutputStream。這類串流類別主要是因應國際化的趨勢,為方便處理 16 位元的Unicode 字
7、元而設的,而且字元串流也會自動分辦資料中的 8 位元 ASCII 字元和 Unicode 字元,不會將兩種資料弄混。16字元串流字元串流l字元串流類別的架構和位元串流有些類似,而且其中各類別、方法的用法也都和位元串流中對應的類別、方法相似,所以學會一種用法就等於學會兩種。不過 Reader、Writer 的衍生類別數量較少:17字元串流字元串流18字元串流字元串流1916-3 輸出、輸入資料輸出、輸入資料l標準輸出、輸入l檔案輸出、輸入l讀寫二元檔20標準輸出、輸入標準輸出、輸入l所謂標準輸出一般就是指螢幕,而標準輸入則是指鍵盤,在前幾章的程式中,就是從鍵盤取得使用者輸入的資料,從螢幕輸出訊息
8、及執行結果。21標準輸出標準輸出 在 System 類別中,有兩個 PrintStream 類別的成員:lout 成員成員:代表標準輸出標準輸出裝置,一般而言,都是指電腦螢幕。不過我們可以利用轉向的方式,讓輸出的內容是輸出到檔案、印表機、或遠端的終端機等等。例如在命令提示字命令提示字元元視窗中,可以用“dir test”的方式,使 dir 原本會顯示在螢幕上的資訊轉向存到 test 這個檔案中。(在 Unix/Linux 系統下也可用相同的轉向技巧,例如 ls test)。22標準輸出標準輸出lerr 成員成員:代表標準示誤訊息輸出裝置,同樣預設為螢幕。以往當應用程式執行過程中遇到錯誤並需顯示
9、相關訊息通知使用者,此時就是將訊息輸出到此裝置。雖然 err 與 out 同樣預設為螢幕,但我們將 out 轉向時,err 並不會跟著轉向。23標準輸出標準輸出l舉例來說,如果執行 dir ABC test 這個命令,但資料夾中並無 ABC 這個檔案,此時 dir 指令仍會將 找不到檔案 的示誤訊息顯示在螢幕上,而不會存到 test 檔案中。24標準輸出標準輸出lPrintStream 類別多重定義了適用於 Java 各種資料型別的 print()、println()方法(後者會在輸出資料後再多輸出一個換行字元以進行換行),所以我們能用這兩個方法輸出任何資料型別,Java 都會自動以適當的格式
10、輸出。25標準輸出標準輸出l此外,PrintStream 類別還有一對多重定義的 write()方法,其功能也是輸出位元資料,但此時參數是資料的位元值。例如我們要輸出 A 這個字元,必須指定其 ASCII 碼 65,例如 write(65);。另一個 write()方法則是可輸出位元陣列的元素,且可指定要從第幾個元素開始輸出、共輸出幾個元素:26標準輸出標準輸出lPrintStream 類別有個和其它串流類別不同的特點,就是它的方法都不會拋出 IOException 例外。以下這個簡單的程式示範了這幾個方法的用法及效果:27標準輸出標準輸出28標準輸出標準輸出29標準輸出標準輸出1.第 812
11、 行的迴圈會分別用 print()和 write()方法輸出 a 陣列中的元素。print()方法會將各元素當整數值輸出,所以可正常看到輸出值;使用 write()方法時則是將元素值當成一個 2 進元數值輸出,對螢幕而言,就是將元素值當成 ASCII 碼,然後輸出對應的 ASCII 字元。30標準輸出標準輸出l以 a0 為例,ASCII 碼 10 是換行字元,所以輸出這行後會自動換行;至於ASCII 碼 20 對應的字元則是一個特殊的控制字元,所以 a1 這行後面看不到內容;至於最後一個 a4:160 對應的字碼超出 127(該字元是 a 上面多一撇),所以在中文環境被當成 Big-5 字碼第
12、一碼,但因為無第二碼,因此只輸出一個問號。31標準輸出標準輸出2.第 14 行改用 err 物件以 write()方法輸出 b 陣列的全部內容。由於 ASCII 碼 7 是個特殊的 BEL 字元,它只會讓電腦發出嗶聲,但不會輸出任何字,而 ASCII 碼 32 對應的是空白字元,所以這行敘述只會讓電腦發出三聲嗶聲,但螢幕上看不到任何輸出。32標準輸出標準輸出l若要測試 System.out、System.err 的差異,可改以轉向的方式來執行這個範例程式,例如:33標準輸入標準輸入l標準輸入一般指的是鍵盤,但同樣可以利用轉向的方式從其它裝置來取得。不過細心的讀者或許發現,前幾章的範例程式並未直
13、接用 System.in 這個物件來讀取鍵盤輸入,我們都是另外建立一個 BufferedReader 類別的物件,然後用這個物件來讀取鍵盤輸入。為什麼要這樣做呢?原因很簡單:就是為了方便處理。34標準輸入標準輸入lSystem.in 這個成員是 InputStream 類別的物件,換言之它是將標準輸入當成位元位元串流來處理,所以我們若用它來讀取鍵盤輸入,讀到的都是位元的形式,處理上並不方便(想一下如果要讀取中文或 Unicode 字元,就需進行額外的處理)。此外直接讀取鍵盤輸入串流時,由於電腦鍵盤緩衝區的運作方式,會造成一些不易處理的狀況。35標準輸入標準輸入l為讓讀者瞭解直接使用 Syste
14、m.in 的情況,我們先介紹 InputStream 類別的 read()方法:36標準輸入標準輸入l使用這些方法時,都需處理 IOException 例外,或是單純拋給上層處理。我們就來看一下透過 System.in 物件用這些方法直接讀取鍵盤輸入的情形:37標準輸入標準輸入38標準輸入標準輸入39標準輸入標準輸入40標準輸入標準輸入1.第 9、18 行分別用不同的 read()方法讀取鍵盤輸入的位元資料。2.第 10 行叫用 Character.toString()方法(參見第 17 章)將字元轉成字串。3.第 13、22 行用 Math.pow()方法(參見第 17 章)計算 2 的 N
15、 次方。41標準輸入標準輸入l讀者可能會覺得很奇怪,為何會有如上的執行執行結果結果?最主要的原因是範例程式第 1 次呼叫 read()方法只讀取1個位元,但使用者可能輸入 2 位數字(多個位元)、且 InputStream 的 read()方法也會讀到 Enter 按鍵的資訊所造成的。42標準輸入標準輸入l回頭看第一個執行結果:程式第 1 次要求輸入,我們輸入 2 時,read()方法傳回的是“2”這個字元的 ASCII 碼,也就是 50,所以程式必須進行一些轉換,才能得到整數以進行運算。l程式第 2 次要求輸入時,我們還未輸入,程式就直接顯示例外訊息而結束,這是因為前一次輸入 2 時按下的
16、Enter 鍵會產生歸位(Carriage Return)及換行(Line Feed)字元(控制碼分別 13 及 10)。43標準輸入標準輸入l所以第 2 次讀取時,read()方法便直接讀到這些字元,造成輸入的字串變成空字串,導致第 19 行程式進行轉換時發生例外。l至於第 2 個執行結果,則是在第 1 次輸入時,就故意輸入多個字元。結果第 2 次的 read()方法就讀到前次未讀到的 5,所以就直接計算 2 的 5 次方。44標準輸入標準輸入l雖然 Enter 鍵的問題並非不能解決,但一來這樣做會讓程式多做額外的處理,二來大多數的應用程式都是要求使用者輸入字元而非位元,所以我們會用字元串流
17、來包裝用字元串流來包裝 System.in,達到簡化處理的目的。45用字元串流來包裝用字元串流來包裝 System.inl為了方便我們從鍵盤取得資料,我們會以字元串流來包裝 System.in 這個位元串流,包裝(wrap)意指用 System.in 來建立字元串流字元串流的物件,所以對程式來說,它使用的是 字元 串流,而非原始的 System.in 位元 串流。46用字元串流來包裝用字元串流來包裝 System.inl以前幾章取得鍵盤輸入的方式為例,我們都使用如下的程式:47用字元串流來包裝用字元串流來包裝 System.inl上述程式就是先將 System.in 物件先包裝成 InputS
18、treamReader 物件,然後再包一層變成 BufferedReader 物件,最後才用此物件的 readLine()方法來取得輸入。之所以要包兩層,主要原因可分為 2 點:48用字元串流來包裝用字元串流來包裝 System.inlInputStreamReader 是個特殊的字元串流,它的功用就是從位元串流取得輸入,然後將這些位元解讀成字元。因此在建構 InputStreamReader 物件時,必須以一個位元串流物件為參數來呼叫其建構方法。但 InputStreamReader 在使用上仍有前述 Enter 鍵的問題,操作並不方便。因此一般都會將它再包裝成其它更方便使用的串流類別物件,
19、例如 BufferedReader。49用字元串流來包裝用字元串流來包裝 System.inlBufferedReader 是所謂的緩衝式輸入串流,也就是先將串流的輸入存到一記憶體緩衝區中,程式再到這個緩衝區讀取輸入。在讀取檔案時這種緩衝式輸入效率較佳,而讀取鍵盤輸入時,也可免去處理 Enter 鍵的問題。50用字元串流來包裝用字元串流來包裝 System.inl但 BufferedReader 只有以 Reader 物件為參數的建構方法,因此我們必須先將 System.in 轉成 InputStreamReader 物件,才能用後者呼叫 BufferedReader 的建構方法,產生所要的物
20、件。l使用 BufferedReader 的 readLine()方法讀取輸入時,每次會讀取 一行的內容,且會自動忽略該行結尾的歸位及換行字元,因此可順利解決 Enter 鍵的問題。請參考以下範例:51用字元串流來包裝用字元串流來包裝 System.in52用字元串流來包裝用字元串流來包裝 System.in53用字元串流來包裝用字元串流來包裝 System.in54用字元串流來包裝用字元串流來包裝 System.in1.第 09 行用 InputStreamReader 包裝 System.in。2.第 14 行以 while 迴圈的方式連續讀取多個字元,遇到換行字元(字碼為 10)時即停止
21、。3.第 18、19 行改以 for 迴圈輸出所有讀到的字元。4.第 24 行使用 BufferedReader 包裝第 09 行建立的 InputStreamReader 物件。55用字元串流來包裝用字元串流來包裝 System.inl此外 BufferedReader 仍是有兩個 read()方法可用於特定的字元讀取方式:56檔案輸出、輸入檔案輸出、輸入l在前一節我們透過 System.in 及 System.out 認識一些位元串流及字元串流的基本用法。其實只要稍加變化,我們就能用串流來讀寫檔案了。l如前所述,要進行檔案讀寫,首先要做的就是開啟檔案串流,接著即可用串流的方法進行讀寫,讀寫
22、完畢後則需關閉串流以節省系統資源。57使用字元串流讀取文字檔使用字元串流讀取文字檔l要讀寫檔案,可使用內建的 FileReader/FileWriter 字元串流來處理,如其名稱所示,它們是專為檔案所設計的。l這兩個字元串流的用法都很簡單,分別以檔案名稱為參數呼叫其建構方法即可建立該檔案的串流物件,以下我們先來看 FileReader 的用法。58使用字元串流讀取文字檔使用字元串流讀取文字檔lFileReader 是 InputStreamReader 的子類別,所以可用前一節介紹的 read()方法來讀取串流中的字元。以下就是用 FileReader 讀取文字檔中所有字元並輸出在螢幕上的小程
23、式。59使用字元串流讀取文字檔使用字元串流讀取文字檔60使用字元串流讀取文字檔使用字元串流讀取文字檔61使用字元串流讀取文字檔使用字元串流讀取文字檔62使用字元串流讀取文字檔使用字元串流讀取文字檔1.第 13 行取得使用者輸入的檔名(路徑)字串,第 14 行即以此字串建立 FileReader 物件 fr。2.第 18、19 行以 while 迴圈的方式連續用 fr.read()讀取檔案中的字元,讀到檔案結尾時,read()會傳回-1,即停止迴圈。3.第 21 行呼叫 close()關閉檔案串流。63使用字元串流讀取文字檔使用字元串流讀取文字檔l至於寫入檔案用的 FileWriter 類別則是
24、 OutputStreamWriter 的子類別。請注意,如果在建立寫入串流時,指定了已存在的檔案,則程式會將檔案中原有的資料全部清除,再寫入新的資料。lFileReader 類別並無定義自己的寫入方法,其寫入功能只有繼承自 OutputStreamWriter 的三個 write()方法:64使用字元串流讀取文字檔使用字元串流讀取文字檔65使用字元串流讀取文字檔使用字元串流讀取文字檔l相信這 3 個 write()的用法應不必特別說明了,我們直接來看範例程式的使用情形。以下這個範例程式請使用者輸入新的檔案名稱,並建立 FileReader 寫入串流,接著請使用者輸入字串、整數、浮點數等三種資
25、料,並寫入檔案串流中,最後並輸出檔案內容以比對檢視:66使用字元串流讀取文字檔使用字元串流讀取文字檔67使用字元串流讀取文字檔使用字元串流讀取文字檔68使用字元串流讀取文字檔使用字元串流讀取文字檔69使用字元串流讀取文字檔使用字元串流讀取文字檔70使用字元串流讀取文字檔使用字元串流讀取文字檔1.第 14 行用使用者輸入的檔名路徑建立新的串流物件。雖然訊息提示的是請使用者輸入新檔案,但其實也可輸入現有的檔名,但此舉將會使檔案原有的內容被範例程式寫入的內容覆蓋掉,因此建議不要用既有檔案做測試。2.第 18、23、28 行分別將使用者輸入的資料以字串的格式用 write()方法寫入。71使用字元串流
26、讀取文字檔使用字元串流讀取文字檔3.第 19、24 行以 write()寫入換行字元,模擬輸入 Enter 按鍵的效果。也就是讓輸入的三個字串會分別存在 3 行。若不加這幾行程式,寫入檔案的內容,都會在同一行。4.第 30 行用 flush()方法將所有未寫入的內容立即寫入串流,然後才於 31 行用 close()方法關閉檔案串流。72使用字元串流讀取文字檔使用字元串流讀取文字檔5.第 3337 行另外建立 FileReader 物件讀取檔案內容,並顯示在螢幕上,以檢查剛才的輸入及寫入是否正常。l讀者可發現,直接使用 FileReader/FileWriter 字元串流來處理檔案其實並不方便,
27、簡單如檔案換行的動作也要我們自行用 write()方法寫入個換行字元。73使用字元串流讀取文字檔使用字元串流讀取文字檔l而且我們還只介紹文字檔的部份,若要處理二元檔案(binary file,例如圖形檔),顯然會遇到更多的不便。因此一般在處理檔案串流時,也和使用 System.in 一樣,將檔案串流用較好用的緩衝式的串流包裝起來,以下就來介紹如何透過緩衝式串流來讀寫檔案。74使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流l讀取檔案時,我們同樣可用 BufferedReader 來包裝 FileReader 物件,然後就能用 readLine()方法來做整行的讀取。l至於寫入方面,則可用對
28、應的 BufferedWriter 來包裝 FileWriter 物件,BufferedWriter 除了有和 FileWriter 一樣的三個方法外,還多了一個 newLine()方法可替我們進行換行動作。75效率較佳的緩衝式處理效率較佳的緩衝式處理l使用緩衝式串流來處理檔案讀寫還有一個優點,就是讀寫的效率會比較佳。如果直接以檔案串流讀寫檔案,程式每一個讀寫敘述,都會使系統進行一次讀寫動作;而使用緩衝式讀寫串流,可將一大筆資料都預先讀到緩衝區(記憶體空間),或是等要寫入的資料累積滿整個緩衝區時再一次寫入,如此程式的效能會稍有提昇。76使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流l使用
29、緩衝式寫入串流 BufferedWriter 時,一定要在關閉串流物件前,用 flush()將緩衝區中的資料立即寫入串流,否則會造成關閉串流而仍有資料未寫入的情況。以下就是使用緩衝式串流讀寫檔案的範例:77使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流78使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流79使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流80使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流81使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流82使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流1.第 14、15 行用使用者輸入的檔名路徑建立新 FileW
30、riter 串流物件,再用此物件建立 BufferedWriter 緩衝式字元寫入串流。和前一範例相同,雖然訊息提示的是請使用者輸入新檔案,但其實也可輸入現有的檔名,但此舉將會使檔案原有的內容被範例程式寫入的內容覆蓋掉。83使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流2.第 22、28 行分別以 BufferedWriter 的 write()方法寫入使用者輸入的姓名和電話字串。3.第 33 行判斷使用者輸入的是否為大/小寫的 Y,是就再執行一次迴圈,也就是再讓使用者輸入一筆資料。4.第 35、36 行將緩衝區內容全部寫入,並關閉串流。84使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔
31、案串流5.第 4347 行是建立 BufferedReader 串流物件以讀取檔案內容,並顯示在螢幕上。6.第 45、46 行利用 while 迴圈重複以 BufferedReader 的 readLine()方法讀取檔案的每一行,當讀到的字串為 null 時,即表示已到檔案結尾。85使用緩衝式串流包裝檔案串流使用緩衝式串流包裝檔案串流l此例改用 BufferedReader 的 readLine()方法來讀取檔案內容,就不必像前幾個範例程式一樣,用 Reader 類別的字元讀取方法 read()來讀取了。86讀寫二元檔讀寫二元檔l文字檔可說是為了直接給人看而存在的,給電腦程式用的檔案其實使用
32、二元檔(binary file)就可以了。以 Java 為例,早在第 4 章我們就學過 Java 的各種資料型別,這些資料型態就是可由程式直接取用的。如果連數字都存成字串型式 123456,那 Java 還要自己把它轉成整數或其它數值型別才能進行運算,非常不便。87讀寫二元檔讀寫二元檔l所以如果存程式用的資料也能使用像資料型別的格式,顯然就比存成文字檔方便得多了。而要讓資料以有如資料型別的格式來儲存,就要使用二元檔。l以 123456 為例,若是使用整數格式存放時,其 4 個位元組的值是 00 01 E2 40。如果我們看到這樣的檔案內容,一定無法理解它們是什麼意思,所以說二元檔是給程式(電腦
33、)看的檔案。88讀寫二元檔讀寫二元檔l使用二元檔時,由於很多資料都不是字元,所以通常是以位元串流來處理。在位元串流中,有 FileInputStream 和 FileOutputStream 兩個檔案輸入與輸出串流。但同樣的,直接用這兩個串流來讀寫檔案非常不便,因此我們通常會用 DataInputStream、DataOutputStream 這兩個位元串流包裝檔案串流,然後讀寫二元檔。89讀寫二元檔讀寫二元檔l這兩個類別的特別之處,就在於它們分別實作了 java.io 套件中 DataInput、DataOutput 這兩個介面。90DataOutputStreamlDataOutput 介
34、面定義了一組寫入的方法,而 DataOutputStream 實作了這個介面,方便我們可直接寫入各種 Java 原生資料型別。只要呼叫這些方法,就能將資料以二元的方式寫入串流中。以下所列就是 DataOutputStream 的資料寫入方法:91DataOutputStream92DataOutputStreaml以下就是個簡單的資料寫入程式:93DataOutputStream94DataOutputStream95DataOutputStream1.第 14 17 行以層層包裝的方式,建構程式寫入檔案時所用的DataOutputStream 物件。96DataOutputStream2.第
35、 30 行呼叫 DataOutputStream 的 size()方法傳回寫入的總位元數,此數值應和用 dir 命令所看到的檔案大小數字相同。3.第 31、32 行做最後的清理及關閉串流動作。97DataOutputStreaml執行此程式,輸入檔名後,程式就會將計算結果寫入指定的檔案中,並傳回寫入的位元組數。但因為是以二元檔的格式儲存,所以我們無法用一般文字編輯器讀取其內容,例如用我們先前寫的文字檔讀取程式來讀取程式寫入的檔案,只會看到如 1Aj?0Agg?這些亂碼。98DataInputStreaml要解讀上述的二元檔案,當然是以對應的 DataInputStream 來處理最為方便。Da
36、taInputStream 實作了 DataInput 介面,同理此介面定義了各種資料型別的讀取方法,透過 DataInputStream 物件呼叫這些現成的方法,即可輕鬆從串流讀取各種資料型別。這些方法的名稱也都很一致,幾乎是前述的 writeXXX()方法改成 readXXX()即可,例如:99DataInputStream100DataInputStreaml以下就是我們用 DataInputStream 讀取前一個程式所建立的二元檔的範例程式:101DataInputStream102DataInputStream103DataInputStream104DataInputStream
37、1.第 1417 行以層層包裝的方式,建構程式讀取檔案時所用的 DataIuput Stream 物件。2.第 2129 行以 try 的方式執行讀取檔案及顯示資料的動作。105DataInputStream3.第 2225 行以 while()迴圈持續讀取檔案,其中第 2324 行分別以DataInputStream 的 readInt()、readDouble()方法來讀取檔案中的整數及浮點數資料。4.第 27 行呼叫 DataInputStream 的 skipBytes()跳過 12 個位元組,使程式每讀一筆整數及浮點數資料,就跳過另一筆。因此只會顯示檔案中第單數筆的資料。106Dat
38、aInputStream5.第 30 行的 catch 敘述捕捉 EOFException 檔案結束例外物件,並在第 31 行關閉串流。EOFException 是 IOException 的衍生類別,用來表示已讀到檔案結尾(End Of File,EOF)或串流結尾的例外狀況。107無正負號的整數無正負號的整數lJava 的整數型別都是可存放正負數值,但像 C/C+程式語言都可宣告無正負號(unsigned)的整數。以 16 位元的 short 為例,unsigned short,可存放 065535 的數值,但 Java 的 short 因為也要能表示負數,所以只能表示-327683276
39、7 的數值。108無正負號的整數無正負號的整數l為了讓 Java 程式也能正確讀寫由 C/C+程式讀寫的這類資料,DataInputStream 和 DataOutputStream 各有一對特別的讀寫方法,可讀寫無正負號的整數資料:10916-4 物件的讀寫物件的讀寫lJava 是物件導向的程式語言,因此很多情況我們會需要將物件的資料寫入檔案。如果使用前面學過的方法,以寫入二元檔為例,您必須呼叫 DataOutput Stream 的多個 writeXXX()方法,才能將物件中每個資料成員一一寫入串流。110物件的讀寫物件的讀寫l其實從本章開頭的類別架構圖可發現,在位元串流部分,Java 已
40、提供 ObjectOutputStream、ObjectInputStream 這兩個專用於物件讀寫的串流。它們各有readObject()、writeObject()方法可一次就讀取整個物件的資料,或寫入整個物件。111實作實作 Serializable 介面介面l但是,我們不能任意用這些串流及其方法來讀寫類別物件,必須有實作 java.io 套件中 Serializable 介面的類別,才能用 ObjectXXX 串流物件來讀寫其物件。所幸,實作 Serializable 介面是個相當簡單的動作,因為這個介面未定義任何的方法和成員,所以我們只要在類別定義加上 implements Seri
41、alizable 這幾個字就可以了,完全不需再自訂任何方法。112實作實作 Serializable 介面介面l此外要讀寫物件還需注意一點,因為 ObjectOutputStream 在寫入物件時,也會將類別的資訊記錄下來,所以若要用另一個程式以 ObjectInputStream 將物件讀回來,必須兩個程式中所定義的物件類別完全相同,不能只是有相同的資料成員,必須連方法及其它宣告也都一樣,否則程式在進行讀取時,會引發 ClassNotFoundException(找不到類別)的例外。113寫入物件寫入物件l以下我們就沿用第 14 章 TestCar.java 這個範例程式中的 MyCar 自
42、訂類別,將它宣告為 implements Serializable:114寫入物件寫入物件115寫入物件寫入物件116寫入物件寫入物件l接著我們就能寫一個程式,利用 ObjectOutputStream 串流物件將汽車物件寫到指定的檔案中。以下這個範例程式會先請使用者輸入愛車的油量及耗油率資訊,並以之建立 MyCar 物件,然後再用 ObjectOutputStream 串流物件將之寫入 mycar 這個檔案。117寫入物件寫入物件118寫入物件寫入物件119寫入物件寫入物件120寫入物件寫入物件1.第 23、24 行將 FileOutputStream 物件包裝成 ObjectOutputS
43、tream 物件。2.第 26 行以 ObjectOutputStream 的 writeObject()方法將物件寫入串流中。3.第 27、28 行呼叫將串流中所有資料立即寫入並關閉串流。121寫入物件寫入物件l若以一般文書編輯器開啟程式寫入的檔案 mycar,將會看到一團亂碼,因為 ObjectOutputStream 是以二元檔的方式將物件寫入檔案中,要讀回檔案中的物件資訊,可用 ObjectInputStream 串流。122從檔案讀取物件資料從檔案讀取物件資料l要將檔案(或其它串流)中的物件資料讀回程式中處理,可使用 ObjectInputStream 的 readObject()方
44、法。l請特別注意,readObject()會拋出 IOException、ClassNotFoundException 這兩個 Checked 例外,所以在呼叫 readObject()的方法中,必須拋出或處理這兩個例外。123從檔案讀取物件資料從檔案讀取物件資料l也因此在讀取物件的範例程式中,必須比讀寫其它串流時,多處理一個 ClassNot FoundException 例外。請參考以下的範例程式:124從檔案讀取物件資料從檔案讀取物件資料125從檔案讀取物件資料從檔案讀取物件資料126從檔案讀取物件資料從檔案讀取物件資料127從檔案讀取物件資料從檔案讀取物件資料1.第 6 行將 main
45、()方法宣告多拋出一個 ClassNotFoundException 例外。2.第 09、10 行將 FileInputStream 包裝成 ObjectInputStream 物件。128從檔案讀取物件資料從檔案讀取物件資料3.第 11 行以 ObjectInputStream 的 readObject()方法將從串流讀回物件。由於此程式只讀一筆物件就自行關閉迴圈,所以未用 try/catch 來執行 readObject()方法,若您要參考前幾個範例程式的作法,讓程式一直讀到檔案結尾,就必須用 try 來執行 readObject()方法,並用 catch 捕捉 EOFException
46、例外物件。129從檔案讀取物件資料從檔案讀取物件資料4.第 12 行關閉串流。5.第 1730 行則是模擬汽車行駛的迴圈,此部份的說明可參見第 14 章。13016-5 綜合演練綜合演練l將學生成績資料存檔l讀取學生成績檔並計算平均131將學生成績資料存檔將學生成績資料存檔l在現實環境中,將物件資料存檔是很實際的應用。本範例程式就是建立一個學生成績資料類別,並提供輸入介面,最後再將輸入的學生成績物件存檔。為方便起見,我們先設計一個存放學生資料的 Student 類別,並存於 Student.java 檔案中。132將學生成績資料存檔將學生成績資料存檔133將學生成績資料存檔將學生成績資料存檔1
47、34將學生成績資料存檔將學生成績資料存檔1.第 3 行用 implements Serializable 宣告此類別可寫入檔案中。2.第 510 行為可設定所有資料成員值的建構方法。135將學生成績資料存檔將學生成績資料存檔3.第 1518 行定義了 4 個可傳回物件中各成員值的方法。4.第 21 行定義了計算及傳回個人平均分數的方法,在下個範例中會用到。5.第 2528 行分別宣告姓名及英文/數學/Java 三科成績的資料成員。l接下來我們就來設計一個程式,可讓使用者輸入學生資料,並將學生資料存檔。136將學生成績資料存檔將學生成績資料存檔137將學生成績資料存檔將學生成績資料存檔138將學
48、生成績資料存檔將學生成績資料存檔139將學生成績資料存檔將學生成績資料存檔140將學生成績資料存檔將學生成績資料存檔141將學生成績資料存檔將學生成績資料存檔1.第 14、15 行將 FileOutputStream 包裝成 ObjectOutputStream 物件。2.第 18 行宣告的 counter 變數是用來記錄使用者共輸入了幾筆學生資料。3.第 2044 行以 do/while 迴圈持續取得使用者輸入的學生資料以建立學生物件,並於第 40 行以 ObjectOutputStream 的 writeObject()方法將物件寫入串流中。142將學生成績資料存檔將學生成績資料存檔4.第
49、 46、47 行呼叫將串流中所有資料立即寫入並關閉串流。5.第 49、50 行顯示程式共寫入幾筆學生資料至檔案中。l程式會一直請使用者輸入學生資料,直到使用者回答不再輸入為止。利用這個程式建立的學生資料檔也是二元檔,我們可用物件讀取串流來讀取這個檔案的內容。143讀取學生成績檔並計算平均讀取學生成績檔並計算平均l這個範例是要讀取檔案中的學生成績並顯示出來,同時還會加總各科分數,以計算各科的平均分數。此程式會用 try/catch 區塊來進行讀取物件的動作,try 區塊中以 while 迴圈持續用 ObjectInputStream 的 readObject()方法讀取物件,當程式讀到檔案結尾時
50、,readObject()方法會拋出 EOFException 例外,此時程式即可計算總平均分數並關閉串流。144讀取學生成績檔並計算平均讀取學生成績檔並計算平均145讀取學生成績檔並計算平均讀取學生成績檔並計算平均146讀取學生成績檔並計算平均讀取學生成績檔並計算平均147讀取學生成績檔並計算平均讀取學生成績檔並計算平均148讀取學生成績檔並計算平均讀取學生成績檔並計算平均149讀取學生成績檔並計算平均讀取學生成績檔並計算平均1.第 15、16 行將 FileInputStream 物件包裝成 ObjectInputStream 物件。2.第 18 行宣告的 counter 變數是用來記錄共