視窗程式設計 1. Java 進階回顧(1): 檔案處理 Reference: 1. Java 2 視窗程式設計,文魁出版社,位元文化編著 (2008) (教科書) 2. 深入研究Java Swing,上奇資訊股份有限公司,黃嘉輝著 (2011) 3. Java SE 6.0視窗程式設計之道,碁峰出版社,黃嘉輝著 (2008) 4. Java 初學指引,博碩文化,陳錦輝著 (2010) 視窗程式設計 1. Java 進階回顧(1): 檔案處理 Chih Hung Wang
Java的檔案處理 除了程式本身的資料運算之外,Java將其餘由外部輸入或 輸出到外部設備的資料交由java.io類別庫的類別來處理。 例如檔案、印表機等都屬於java.io類別庫的服務範圍。 Java將檔案的處理,視為一個串流(stream),不論在哪種 硬體及作業系統環境下,檔案都被看成是由眾多字元 (characters)或位元(bits)所組成的串流,因此程式設計師 在進行檔案方面的處理時,面對的其實是一個資料串流, 如圖12-1示意。
Java的檔案處理 串流分為輸入串流(input stream)與輸出串流(output stream), 分別用來對應資料的讀取與寫入。 Java將處理串流的工作交由java.io類別庫完成,該類別庫是可以 讓Java進行所有IO的處理,而檔案也屬於其中一種,我們可以 在需要進行檔案讀寫時,載入該類別庫內的特定類別或者將整 個java.io類別庫載入。 檔案是一種資料串流
Java的檔案處理 檔案類型與類別 對於Java而言,檔案分為兩種:文字檔(Text file)與二進位檔 (Binary file),其特色如下 純文字檔: 方便閱讀,但較無保密性。其他使用者也可以透過純文字編輯器開啟 並成功閱讀。在Java程式中,純文字檔可使用Reader與Writer類別來 進行讀寫工作。 二進位檔: I/O處理速度較快並具有保密性,但檔案內容需透過程式轉譯才能閱 讀。二進位檔的資料是由一連串的位元或位元組所組合,通常使用在 某些特殊用途,例如圖檔。在Java程式中,二進位檔可透過 InputStream與OutputStream類別來進行讀寫工作。
Java的檔案處理 【註】 純文字檔也可以透過InputStream與OutputStream類別進行讀寫 工作。這是因為,所有的檔案實際上都是以01等二進制檔案格 式存放於磁碟中,只不過純文字檔的位元編碼具有特殊結構, 當使用InputStream與OutputStream類別進行純文字檔的讀寫工 作時,將忽略其編碼格式,例如複製檔案時,就可以適用。 但若要將純文字檔以文字模式在Java程式中輸出,則仍以 Reader與Writer類別較為適當。
Java的檔案處理 事實上,Java程式在進行檔案的讀寫工作時,一般並未使 用上述的四種類別直接進行工作,而是使用其子孫類別 進行工作,因為這些子孫類別已經繼承了上述四種類別 的重要方法 例如,我們會使用FileReader類別讀取純文字檔,而當工作完 畢欲將檔案關閉時所使用的close()方法,就是繼承自Reader類 別。 本章所介紹的相關類別繼承圖如圖12-2、12-3中底色類別所示。
Java的檔案處理 與IO有關的部分類別繼承圖(底色為本章與純文字檔案有關之類別)
Java的檔案處理 與IO有關的部分類別繼承圖(底色為本章與二進位檔案有關之類別)
字元串流的檔案處理 字元串流亦即串流內以字元為單位,此處的字元指的是 Unicode,因此,只有純文字檔適合使用字元串流進行讀 寫。 Reader與Writer類別是以字元串流方式來處理純文字檔,這兩 個類別提供了關於讀寫純文字檔的基本成員函式,如表12-1、 12-2 但我們一般並不會直接使用這兩個類別的物件,而是使用其衍 生類別的物件進行實際的讀寫工作。 當然,由於這些類別繼承自Reader/Writer類別,因此也可以使用表 12-1、12-2的各種成員函式。
字元串流的檔案處理 由表12-1、12-2中,我們可以發現Reader/Writer類別的有些成 員函式被宣告為abstract,代表該類別無法直接產生物件,必 須繼承後實作這些abstract函式才能產生物件。 因此我們一般都是使用Reader/Writer類別的衍生類別來產生物件,並 且這些衍生類別已經實作了abstract函式,並不需要我們自行實作。 函式宣告 用途 回傳值說明 abstract void close() 關閉字元串流。 無 int read() 讀取字元串流中的單一個字元。 -1代表串流已讀取完畢。其餘正整數或0則代表讀取字元成功。 int read(char[] cbuf) 讀取字元串流中的資料,並將之放入cbuf字元陣列中。 回傳值為讀取字元的數量,-1代表串流已讀取完畢。 abstract int read(char[] cbuf, int off, int len) 讀取字元串流中的資料,將之放入cbuf字元陣列,並由cbuf[off]開始存放,且指定最多讀取len個字元。 long skip(long n) 略過n個字元不讀取。 實際被略過的字元數量。
字元串流的檔案處理 Writer append(char c) 在串流內增加一個字元在末端。 回傳已經增加字元在末端的串流。 函式宣告 用途 回傳值說明 Writer append(char c) 在串流內增加一個字元在末端。 回傳已經增加字元在末端的串流。 abstract void close() 關閉字元串流。 無 abstract void flush() 將緩衝區的內容寫入到檔案。當使用緩衝區時,必須記得使用此方法,否則資料僅會存放在串流中而不會寫入到檔案內。 void write(char[] cbuf) 將cbuf字元陣列的內容寫入串流。 abstract void write(char[] cbuf, int off, int len) 將字元陣列由cbuf[off]開始寫入len個字元到串流。 void write(int c) 寫入單一個字元到串流。 void write(String str) 寫入一個字串到串流。 void write(String str, int off, int len) 寫入一個字串的第off~off+len-1個字元到串流。
字元串流的檔案處理 FileReader類別 InputStreamReader為Reader的子類別,FileReader為 InputStreamReader的子類別(見圖12-2),我們將使用 FileReader作為讀取文字檔案的主要類別,因此,可使用的方 法也包含祖先類別的方法。 任何對於檔案內讀寫動作可以分為三個步驟:分別是開檔、讀 寫、關檔。 其中,開檔動作包含在建立檔案物件時的建構子,例如FileReader的 建構子如下:
字元串流的檔案處理 FileReader類別 【說明】: 由於前兩個建構子的引數都是本書未介紹的類別型態,因此不多作介 紹 本書僅介紹第三類建構子FileReader(String fileName),其中,fileName 代表檔案路徑及檔名的字串,我們可以使用絕對路徑或相對路徑來表 示。 Windows與Unix的目錄分隔符號不同,Unix/Linux為「/」、Windows為 「\」,而「\」在Java字串中,被視為控制字元,故需要使用兩個「\」來 表示目錄分隔符號。 FileReader(File file) FileReader(FileDescriptor fd) FileReader(String fileName) //上述建構子會拋出FileNotFoundException例外,它是IOException例外類別的子類別
字元串流的檔案處理 FileReader類別 【實用範例12-1】:開啟純文字檔,並使用字元串流方式讀取 文字檔內容後輸出並統計所讀取的字元個數。 範例12-1:ch12_01.java(隨書光碟 myJava\ch12\ch12_01.java) 1 2 3 4 5 6 7 8 9 10 11 12 /* 檔名:ch12_01.java 功能:以字元串流讀取文字檔 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_01 //主類別 { public static void main(String args[]) throws IOException,FileNotFoundException
字元串流的檔案處理 FileReader類別 text1.text(隨書光碟 myJava\ch12\file\text1.txt) 13 14 15 16 17 18 19 20 21 22 23 char cbuf[] = new char[256]; FileReader fr = new FileReader("c:\\myJava\\ch12\\file\\text1.txt"); int num = fr.read(cbuf); //讀取最多256個字元到cbuf陣列中 String str1 = new String(cbuf,0,num); //字元陣列轉換為字串 System.out.println("總共讀取" + num + "個字元數"); System.out.println("檔案內容如下"); System.out.println(str1); fr.close(); //關檔 }
字元串流的檔案處理 FileReader類別 執行結果: 範例說明: (1)FileReader建構子會產生FileNotFoundException例外,read()會產生 IOException例外,所以在第11行,使用throws讓系統捕捉例外。 (2)第14行記錄了目標文字檔的路徑及檔名。文字檔的內容如上圖。 (3)第16行,透過read讀取串流內的字元,由於cbuf字元陣列的大小為 256,因此一次會讀取256個字元,如果不足,則讀取至最後一個字元 為止,並且回傳值將會是實際讀取到的字元數量。 總共讀取67個字元數 檔案內容如下 S9703501 王大民 89 84 75 S9703502 郭小志 77 69 87 S9703503 胡小龍 65 68 77
字元串流的檔案處理 FileReader類別 範例說明: (4)由執行結果中,可以發現一共讀取到67個字元,這是因為文字檔 中,第一行與第二行分別有(21+2)個字元,最後一行則有21個字元。 對於Java而言,不論中英文都算是一個字元。並且,前兩行多的兩個 字元分別是「\r」與「\n」控制字元,在字元表中,分別代表的是 Carriage Return(返回該行第一格)及NewLine(換行),而 Windows在文字檔的每個換行處都會加上這兩個符號,當純文字編輯 器讀取到這兩個符號時,就會顯示出換行的效果。 (5)第21行是關檔。
字元串流的檔案處理 FileReader類別 【實用範例12-2】:使用迴圈讀取純文字檔的所有內容。 範例12-2:ch12_02.java(隨書光碟 myJava\ch12\ch12_02.java) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* 檔名:ch12_02.java 功能:使用迴圈讀取文字檔的全部內容 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_02 //主類別 { public static void main(String args[]) throws IOException,FileNotFoundException char cbuf[] = new char[16]; FileReader fr = new FileReader("c:\\myJava\\ch12\\file\\text1.txt");
字元串流的檔案處理 FileReader類別 15 16 17 18 19 20 21 22 23 24 25 26 int num; String str1; while((num = fr.read(cbuf)) !=-1) //使用迴圈讀取文字檔的全部內容 { str1 = new String(cbuf,0,num); //字元陣列轉換為字串 System.out.println("總共讀取" + num + "字元數"); System.out.println(str1); System.out.println("-----------------------"); } fr.close(); //關檔
字元串流的檔案處理 FileReader類別 執行結果 範例說明: (1)這個範例和範例12-1使用相同的目標文 字檔,但這次我們將cbuf字元陣列的大小 設定為16,明顯不足以一次讀取完畢字 元串流的所有內容。因此在第17~23行, 使用迴圈來分次讀取字元串流的內容, 每次會自動讀取最多16個字元。 (2)在執行結果中,可以發現每次讀取的 字元數量,最後一次為3個字元,然後再 嘗試讀取時,將會獲得-1回傳值而離開迴 圈。 總共讀取16字元數 S9703501 王大民 89 ----------------------- 84 75 S9703502 郭小志 77 69 87 S9 703503 胡小龍 65 68 總共讀取3字元數 77
字元串流的檔案處理 FileWriter類別 相對於FileReader的讀取功能,FileWriter類別則提供寫入文字 檔的功能。同樣地,OutputStreamWriter為Writer的子類別, FileWriter為OutputStreamWriter的子類別(見圖12-2),因此, FileWriter產生的物件也可以使用祖先類別的方法。 寫入的開檔動作同樣包含在建構子,FileWriter的建構子如下: FileWriter的建構子 FileWriter(File file) FileWriter(File file, boolean append) FileWriter(FileDescriptor fd) FileWriter(String fileName) FileWriter(String fileName, boolean append) //上述建構子會拋出IOException例外
字元串流的檔案處理 FileWriter類別 【說明】: 【實用範例12-3】:將資料使用字元串流方式寫入純文字檔。 前三個建構子的引數都是本書未介紹的類別型態,因此不多作介紹, 本書僅介紹後兩類建構子,其中,fileName代表檔案路徑及檔名的字 串 而append則是指定是否覆蓋檔案原有內容,若欲覆蓋原有檔案內容, 則將之設定為false或不設定;若希望將新資料加在舊有資料後面,則 設定為true。 【實用範例12-3】:將資料使用字元串流方式寫入純文字檔。 範例12-3:ch12_03.java(隨書光碟 myJava\ch12\ch12_03.java)
字元串流的檔案處理 FileWriter類別 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /* 檔名:ch12_03.java 功能:以字元串流寫入文字檔 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_03 //主類別 { public static void main(String args[]) throws IOException String str1 = "費氏數列如下:"; char endCh[] = {'C','o','n','t','i','n','u','e','.','.','.'}; int numF; FileWriter fw = new FileWriter("c:\\myJava\\ch12\\file\\text2.txt"); fw.write(str1); fw.write('\r'); fw.write('\n'); //寫入換行字元 for(int i=1;i<10;i++)
字元串流的檔案處理 FileWriter類別 執行結果 程式執行完畢,會在 c:\myJava\ch12\file\目錄 下產生text2.txt檔,如圖 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 numF=Fib(i); fw.write(numF+" "); //int與字串連結,會自動轉型為字串 } fw.write(endCh); fw.close(); //關檔 public static int Fib(int n) { if((n==1) || (n==0)) return n; else return Fib(n-1)+Fib(n-2);
字元串流的檔案處理 FileWriter類別 範例說明: (1)範例執行完畢,會產生text2.txt,內容如上圖,其中的換行效果, 是程式第19行的作用。 (2)第18行寫入字串。第27行寫入字元陣列。第19行則是寫入單一字 元兩次。這三種是writer函式利用多載(overload)功能提供的三種引數 列。 (3)第24行,由於數值與字串透過「+」法運算時,執行的是字串連結, 因此結果為字串,所以使用的是write(String str)函式。 (4)本範例如果檔案原本不存在,則會建立新檔案。如果檔案原本已 經存在,則原始檔案內容會被新的內容覆蓋。
字元串流的檔案處理 FileWriter類別 【實用範例12-4】:在原有文字檔案後面增加資料。 範例12-4:ch12_04.java(隨書光碟 myJava\ch12\ch12_04.java) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* 檔名:ch12_04.java 功能:加入文字在文字檔後方 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_04 //主類別 { public static void main(String args[]) throws IOException String str1 = "費氏數列是一個無限數列";
字元串流的檔案處理 FileWriter類別 執行結果(請先執行範例12-3後才執行本範例) 範例說明: 第15行建立FileWriter物件fw時,在建構子設定了append引數為true, 因此原始檔案內容不會被覆蓋。第16、17行為本範例新增的文字,其 中換行字元也需要自行增加。 15 16 17 18 19 20 21 FileWriter fw = new FileWriter("c:\\myJava\\ch12\\file\\text2.txt",true); fw.write('\r'); fw.write('\n'); //寫入換行字元 fw.write(str1); fw.close(); //關檔 }
字元串流的檔案處理 FileWriter類別 【實用範例12-5】:複製文字檔。 範例12-5:ch12_05.java(隨書光碟 myJava\ch12\ch12_05.java) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* 檔名:ch12_05.java 功能:複製文字檔 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_05 //主類別 { public static void main(String args[]) throws IOException,FileNotFoundException char cbuf[] = new char[1]; FileReader fr = new FileReader("c:\\myJava\\ch12\\file\\text1.txt"); FileWriter fw = new FileWriter("c:\\myJava\\ch12\\file\\text3.txt"); int num; String str1;
字元串流的檔案處理 FileWriter類別 執行結果(執行後出現text3.txt,內容與text1.txt相同) 範例說明: 第18~19行使用迴圈逐一複製文字檔內容,由於cbuf的陣列大小為1, 故一次複製一個字元。 18 19 20 21 22 23 while((num = fr.read(cbuf)) !=-1) //使用迴圈逐一複製文字檔內容 fw.write(cbuf); fr.close(); //關檔 fw.close(); //關檔 }
字元串流的檔案處理 緩衝區 讀寫檔案時,可以採用緩衝區方式進行處理,減少對磁碟的存 取動作(磁碟存取屬於機械動作,故較慢) 所謂緩衝區,代表的是在記憶體中預留一個空間作為緩衝區,當進行 存取檔案時,會對緩衝區進行存取。 若讀取檔案時,緩衝區已無資料可讀,則會從磁碟檔案中載入資料 當緩衝區作為寫入使用時,若緩衝區已滿,則會自動將資料寫入磁碟檔案 中,以清空緩衝區,使得程式仍舊可以對緩衝區寫入資料 如下圖示意。 而提供緩衝區功能的類別則為BufferedReader及BufferedWriter,其繼承關 係請見圖12-2,12-3。
字元串流的檔案處理 緩衝區 使用緩衝區讀寫檔案示意圖
字元串流的檔案處理 緩衝區 BufferedReader BufferedReader的建構子 【說明】: BufferedReader繼承自Reader類別,在執行建構子時,可以指定對應 的檔案及緩衝區大小。建構子如下: BufferedReader的建構子 【說明】: 上述建構子可以產生輸入字元串流對應的緩衝區,雖然in引數被宣告 為Reader類別,但我們可以傳入FileReader類別的物件,因為 FileReader是Reader的子孫類別。 而sz引數則為緩衝區的大小(sz大小需適中,否則使用緩衝區的意義 不大),若未設定sz引數,則使用預設的緩衝區大小(JDK1.4預設為 8192個字元)。 BufferedReader(Reader in) BufferedReader(Reader in, int sz)
字元串流的檔案處理 緩衝區 當我們使用緩衝區方式來讀取檔案資料時,可以透過 BufferedReader的眾多方法,進行更方便的讀取動作,方法如 表12-3 其中有些在Reader類別已經被定義,但在BufferedReader類別被改寫, 有些則是新增的方法: 函式宣告 用途 回傳值說明 void close() 關閉字元串流。 無 int read() 讀取單一個字元。 -1代表串流已讀取完畢。其餘正整數或0則代表讀取字元成功。 int read(char[] cbuf) 讀取字元串流中的資料,並將之放入cbuf字元陣列中。 回傳值為讀取字元的數量,-1代表串流已讀取完畢。
字元串流的檔案處理 緩衝區 【實用範例12-6】:使用緩衝區讀取純文字檔內容,一次讀取 一行直到讀取完畢為止。 範例12-6:ch12_06.java(隨書光碟 myJava\ch12\ch12_06.java) 函式宣告 用途 回傳值說明 int read(char[] cbuf, int off, int len) 讀取字元串流中的資料,將之放入cbuf字元陣列,並由cbuf[off]開始存放,且指定最多讀取len個字元。 回傳值為讀取字元的數量,-1代表串流已讀取完畢。 long skip(long n) 略過n個字元不讀取。 實際被略過的字元數量。 public String readLine() 新增的方法,可以一次讀取一行字串 回傳一行字串,若已讀取完畢,則回傳null。
字元串流的檔案處理 緩衝區 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /* 檔名:ch12_06.java 功能:使用緩衝區讀取文字檔 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_06 //主類別 { public static void main(String args[]) throws IOException,FileNotFoundException String str1; FileReader fr = new FileReader("c:\\myJava\\ch12\\file\\text1.txt"); BufferedReader bufferIn = new BufferedReader(fr); while((str1=bufferIn.readLine())!=null) System.out.println(str1); } fr.close(); //關檔
字元串流的檔案處理 緩衝區 執行結果: 範例說明: 第17行使用readLine()讀取一行資料,當文字檔內容讀取完畢,則會 回傳null。由執行結果可以看出,讀取該行資料時,回傳的字串並不 包含換行字元。(執行結果的換行效果是由println產生) S9703501 王大民 89 84 75 S9703502 郭小志 77 69 87 S9703503 胡小龍 65 68 77
字元串流的檔案處理 緩衝區 BufferedWriter BufferedWriter的建構子 【說明】: BufferedWriter繼承自Writer類別,在執行建構子時,可以指定對應 的檔案及緩衝區大小。建構子如下: BufferedWriter的建構子 【說明】: 上述建構子可以產生輸出字元串流對應的緩衝區,雖然out引數被宣 告Writer類別,但我們可以傳入FileWriter類別的物件,因為 FileWriter是Writer的子孫類別。 而sz引數則為緩衝區的大小(sz大小需適中,否則使用緩衝區的意義 不大),若未設定sz引數,則使用預設的緩衝區大小(JDK1.4預設為 8192個字元)。 BufferedWriter(Writer out) BufferedWriter(Writer out, int sz)
字元串流的檔案處理 緩衝區 當我們使用緩衝區方式來寫入檔案資料時,可以透過 BufferedWriter的眾多方法,進行更方便的寫入動作,方法如 表12-4 其中有些在Writer類別已經被定義,但在BufferedWriter類別被改寫 (僅繼承而未改寫者,則不列入表內),有些則是新增的方法: 函式宣告 用途 回傳值說明 void close() 關閉字元串流,在關閉之前必須進行flush動作,才會將最後一筆資料寫入檔案。 無 void flush() 將緩衝區的內容立刻寫入到檔案。 void newLine() 寫入換行字元。 void write(char[] cbuf, int off, int len) 將字元陣列由cbuf[off]開始寫入len個字元到串流緩衝區。
字元串流的檔案處理 緩衝區 【觀念及實用範例12-7】:使用緩衝區寫入純文字檔內容,並 且觀察緩衝區大小與flush()功能。 範例12-7:ch12_07.java(隨書光碟 myJava\ch12\ch12_07.java) 函式宣告 用途 回傳值說明 void write(int c) 寫入單一個字元到串流緩衝區。 無 void write(String s, int off, int len) 寫入一個字串的第off~off+len-1個字元到串流緩衝區。 1 2 3 4 5 6 7 /* 檔名:ch12_07.java 功能:使用緩衝區寫入文字檔 */ package myJava.ch12; import java.lang.*; import java.io.*;
字元串流的檔案處理 緩衝區 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class ch12_07 //主類別 { public static void main(String args[]) throws IOException String str1 = "費氏數列如下:"; char endCh[] = {'C','o','n','t','i','n','u','e','.','.','.'}; int numF; FileWriter fw = new FileWriter("c:\\myJava\\ch12\\file\\text4.txt"); BufferedWriter bufferOut = new BufferedWriter(fw,20); bufferOut.write(str1); bufferOut.newLine(); //寫入換行字元 for(int i=1;i<10;i++) numF=Fib(i); bufferOut.write(numF+" "); //int與字串連結,會自動轉型為字串 } bufferOut.write(endCh); bufferOut.flush(); //重要,請看註解 fw.close(); //關檔
字元串流的檔案處理 緩衝區 執行結果 程式執行完畢,會在c:\myJava\ch12\file\目錄下產生text4.txt檔,內容 如下 31 32 33 34 35 36 37 38 public static int Fib(int n) { if((n==1) || (n==0)) return n; else return Fib(n-1)+Fib(n-2); }
字元串流的檔案處理 緩衝區 範例說明: (1)第20行利用newLine()函式寫入該作業系統認定的換行字元。 (2)第17行建立的緩衝區大小為20。因此,欲寫入的全部資料將大於 緩衝區的大小。第28行,利用flush()函式要求立即將緩衝區的內容寫 入檔案,然後在第29行關閉檔案。若將第28行設定為註解,則執行結 果中,最後面的「...」三個字元將不會被寫入檔案內,讀者可以自行 測試看看。 (3)由這個範例,我們可以得知,緩衝區資料寫入檔案的時機可以分 為(1)當緩衝區已滿時,自動寫入檔案內。(2)透過flush()強制立即將緩 衝區內的資料寫入檔案。無論使用那一種方式寫入檔案,緩衝區在資 料被寫入檔案後,都將被清空,以便容納新寫入的資料。
字元串流的檔案處理 緩衝區 範例說明: (4)緩衝區的大小決定了檔案的寫入次數,例如若將本範例第17行的 緩衝區大小設定為「1」,則第28行可以省略,因為緩衝區一旦寫入 資料就會立刻額滿而必須自動寫入檔案中,這樣一來,是否使用緩衝 區就變得沒有意義(因為無法減少實際寫入檔案的次數),但若將緩 衝區設定過大,則必須系統提供夠多的記憶體可使用,因此適中的緩 衝區大小必須視系統執行環境以及資料量的大小來決定,若無法預測 者,建議還是使用預設的緩衝區大小即可。
位元串流的檔案處理 有些檔案的內容並非文字,例如圖檔等,此時要讀寫此 類檔案則應該使用位元串流。 InputStream與OutputStream類別是以位元串流方式來處理 檔案,這兩個類別提供了關於讀寫檔案的基本成員函式 ,如表12-5、12-6
位元串流的檔案處理 同樣地,我們一般並不直接使用這兩個類別的物件,而是使用 其衍生類別的物件進行實際的讀寫工作。當然,由於這些類別 繼承自InputStream/OutputStream類別,因此也可以使用表12-5 、12-6的各種成員函式。 而在Java中讀取位元串流將以「位元組」為基本單位,而非位 元。但位元組也是由位元構成,因此一般我們仍以位元串流來 稱呼。
InputStream的常用成員函式(上述方法會拋出IOException例外) 位元串流的檔案處理 函式宣告 用途 回傳值說明 int available() 取得串流內可讀取或可略過的位元組數量。通常可用來代表檔案大小。 串流內可讀取或可略過的位元組數量。 void close() 關閉位元串流。 無 abstract int read() 讀取串流內的一個位元組資料。 回傳值代表為讀取位元組的值(0~255),若回傳-1代表串流已讀取完畢。 int read(byte[] b) 讀取位元串流中的資料,將之放入b位元組陣列。 回傳值為讀取位元組的數量,-1代表串流已讀取完畢。 int read(byte[] b, int off, int len) 讀取位元串流中的資料,將之放入b位元組陣列,並由b[off]開始存放,且指定最多讀取len個位元組。 long skip(long n) 略過n個位元組不讀取。 實際被略過的位元組數量。 InputStream的常用成員函式(上述方法會拋出IOException例外)
位元串流的檔案處理 OutputStream的常用成員函式(下列方法會拋出IOException例外) 函式宣告 用途 回傳值說明 void close() 關閉位元串流。 無 void flush() 將緩衝區的內容寫入到檔案。當使用緩衝區時,必須記得使用此方法,否則資料僅會存放在串流中而不會寫入到檔案內。 void write(byte[] b) 將b位元組陣列的內容寫入串流。 void write(byte[] b, int off, int len) 將位元組陣列由b[off]開始寫入len個位元組到串流。 abstract void write(int b) 寫入單一個位元組到串流。(b的前24位元忽略) OutputStream的常用成員函式(下列方法會拋出IOException例外)
位元串流的檔案處理 同樣地,由於InputStream/OutputStream類別的某些成員函式被 宣告為abstract,因此無法直接產生物件,必須繼承後實作這 些abstract函式才能產生物件。 而我們一般都是使用InputStream/OutputStream類別的衍生類別來產生 物件,並且這些衍生類別已經實作了abstract函式,並不需要我們自 行實作。
FileInputStream與FileOutputStream類別 位元串流的檔案處理 FileInputStream與FileOutputStream類別 FileInputStream與FileOutputStream分別為 InputStream/OutputStream的子類別(見圖12-3),我們將使用 FileInputStream與FileOutputStream作為讀取與寫入檔案的主要 類別,因此,可使用的方法也包含父類別的方法。 請特別注意,使用位元串流可以讀取非文字檔,也可以讀取文字檔, 因為文字檔在檔案中仍舊是以位元方式存在,只不過文字檔的位元組 存放方式具有特殊排列的意義,而當使用位元串流讀取檔案資料時, 我們通常不關心位元組資料的實際意義。
FileInputStream與FileOutputStream類別 位元串流的檔案處理 FileInputStream與FileOutputStream類別 同樣地,對於一般檔案的讀寫動作也是分為三個步驟:分別是 開檔、讀寫、關檔。 其中,開檔動作包含在建立檔案物件時的建構子。 FileInputStream的建構子 【說明】: 同樣地,本書僅介紹第三類建構子FileInputStream (String fileName), 其中,fileName為代表檔案路徑及檔名的字串,我們可以使用絕對路 徑或相對路徑來表示。 FileInputStream(File file) FileInputStream(FileDescriptor fdObj) FileInputStream(String name) //上述建構子會拋出FileNotFoundException例外,它是IOException例外類別的子類別
FileInputStream與FileOutputStream類別 位元串流的檔案處理 FileInputStream與FileOutputStream類別 FileOutputStream的建構子 【說明】: 同樣地,本書僅介紹後兩類建構子,其中,fileName為代表檔案路徑 及檔名的字串,而append則是指定是否覆蓋檔案原有內容,若欲覆蓋 原有檔案內容,則將之設定為false或不設定;若希望將新資料加在舊 有資料後面,則設定為true。 FileOutputStream(File file) FileOutputStream(File file, boolean append) FileOutputStream(FileDescriptor fdObj) FileOutputStream(String name) FileOutputStream(String name, boolean append) //上述建構子會拋出IOException例外
FileInputStream與FileOutputStream類別 位元串流的檔案處理 FileInputStream與FileOutputStream類別 【實用範例12-8】:透過位元串流複製圖片檔。 範例12-8:ch12_08.java(隨書光碟 myJava\ch12\ch12_08.java) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /* 檔名:ch12_08.java 功能:複製檔案 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_08 //主類別 { public static void main(String args[]) throws IOException,FileNotFoundException byte byteData[] = new byte[1]; FileInputStream fi = new FileInputStream("c:\\myJava\\ch12\\file\\pic1.jpg"); FileOutputStream fo = new FileOutputStream("c:\\myJava\\ch12\\file\\pic2.jpg"); int fileSize=fi.available();
FileInputStream與FileOutputStream類別 位元串流的檔案處理 FileInputStream與FileOutputStream類別 pic1.jpg(隨書光碟 myJava\ch12\file\pic1.jpg) 17 18 19 20 21 22 23 24 int num; while((num = fi.read(byteData)) !=-1) //使用迴圈逐一複製每一個位元組 fo.write(byteData); System.out.println("檔案大小=" + fileSize + "位元組複製完畢"); fi.close(); //關檔 fo.close(); //關檔 }
FileInputStream與FileOutputStream類別 位元串流的檔案處理 FileInputStream與FileOutputStream類別 檔案大小 在檔案總管選取此檔按滑鼠右鍵 ,執行快顯功能表的[內容]指令
FileInputStream與FileOutputStream類別 位元串流的檔案處理 FileInputStream與FileOutputStream類別 執行結果: 範例說明: (1)範例執行完畢,會在myJava\ch12\file\目錄中出現pic2.jpg檔,大小同 為207000位元組。 (2)因byteData位元組陣列大小被宣告為1,因此最多只能存放一個位 元組,第18~19行透過迴圈,每次複製一個位元組資料。 (3)第16行,available()函式回傳串流中可供讀取的最大位元組數量。 檔案大小=20700位元組複製完畢
位元串流的檔案處理 緩衝區 BufferedInputStream的建構子 【說明】: 而size引數則為緩衝區的大小(size大小需適中,否則使用緩衝區的意 義不大),若未設定size引數,則使用預設的緩衝區大小(JDK1.4預 設為2048個位元組)。 BufferedInputStream(InputStream in) BufferedInputStream(InputStream in, int size)
位元串流的檔案處理 緩衝區 當我們使用緩衝區方式來讀取檔案資料時,可以透過 BufferedInputStream及其父類別FilterInputStream的眾多方法, 進行更方便的讀取動作,BufferedInputStream的方法如表12-7 其中有些在父類別或祖父類別已經被定義,但在BufferedInputStream 類別被改寫,有些則是新增的方法:
位元串流的檔案處理 緩衝區 BufferedInputStream的常用成員函式(上述方法會拋出IOException例外) 函式宣告 用途 回傳值說明 int available() 取得串流內可讀取或可略過的位元組數量。通常可用來代表檔案大小。 串流內可讀取或可略過的位元組數量。 void close() 關閉位元串流。 無 int read() 讀取單一個位元組。 -1代表串流已讀取完畢。其餘0~255則為所讀取的位元組。 int read(byte[] b, int off, int len) 讀取位元串流中的資料,將之放入b位元組陣列,並由b[off]開始存放,且指定最多讀取len個位元組。 回傳值為讀取位元組的數量,-1代表串流已讀取完畢。 long skip(long n) 略過n個位元組不讀取。 實際被略過的位元組數量。 BufferedInputStream的常用成員函式(上述方法會拋出IOException例外)
位元串流的檔案處理 緩衝區 BufferedOutputStream BufferedOutputStream的建構子 【說明】: BufferedOutputStream繼承自FilterOutputStream,而 FilterOutputStream繼承自OutputStream。在執行建構子時,可以指定 對應的檔案及緩衝區大小。建構子如下 BufferedOutputStream的建構子 【說明】: 上述建構子可以產生輸出位元串流對應的緩衝區,out引數為對應的 位元串流。 而size引數則為緩衝區的大小,若未設定size引數,則使用預設的緩衝 區大小(JDK1.4預設為512個位元組) BufferedOutputStream(OutputStream out) BufferedOutputStream(OutputStream out, int size)
位元串流的檔案處理 緩衝區 當我們使用緩衝區方式來寫入檔案資料時,可以透過 BufferedOutputStream的眾多方法,進行寫入動作,方法如表 12-8 這些方法都在父類別及祖父類別已經被定義,而 BufferedOutputStream類別將之繼承或改寫: 函式宣告 用途 回傳值說明 void flush() 將緩衝區的內容立刻寫入到檔案。 無 void write(byte[] b, int off, int len) 將位元組陣列由b[off]開始寫入len個位元組到串流緩衝區。 void write(int b) 寫入特定位元組資料到串流緩衝區。 BufferedOutputStream的常用成員函式(上述方法會拋出IOException例外)
位元串流的檔案處理 緩衝區 【實用範例12-9】:使用位元串流搭配緩衝區讀取文字檔內容 ,並寫入另一個檔案中。 範例12-9:ch12_09.java(隨書光碟 myJava\ch12\ch12_09.java) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* 檔名:ch12_09.java 功能:複製並輸出檔案 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_09 //主類別 { public static void main(String args[]) throws IOException,FileNotFoundException FileInputStream fi = new FileInputStream("c:\\myJava\\ch12\\file\\text1.txt"); FileOutputStream fo = new FileOutputStream("c:\\myJava\\ch12\\file\\text5.txt"); BufferedInputStream bufIn = new BufferedInputStream(fi);
位元串流的檔案處理 緩衝區 執行結果: 檔案複製完畢,內容如下 S9703501 王大民 89 84 75 16 17 18 19 20 21 22 23 24 25 26 27 28 BufferedOutputStream bufOut = new BufferedOutputStream(fo); int fileSize=fi.available(); byte byteData[] = new byte[fileSize]; bufIn.read(byteData,0,fileSize); bufOut.write(byteData,0,fileSize); String str1 = new String(byteData); //轉換位元組陣列為字串 bufOut.flush(); //記得要flush,否則串流內資料不會寫入檔案中 System.out.println("檔案複製完畢,內容如下"); System.out.println(str1); fi.close(); //關檔 fo.close(); //關檔 } 檔案複製完畢,內容如下 S9703501 王大民 89 84 75 S9703502 郭小志 77 69 87 S9703503 胡小龍 65 68 77
位元串流的檔案處理 緩衝區 範例說明: (1)程式執行完畢,會出現text5.txt,內容與text1.txt完全相同。這說 明了位元串流也可以用來讀取、複製文字檔。因為不論是任何檔案, 都是以0、1位元模式存在於檔案系統中。只不過該0、1位元是否具有 某種編碼格式的差異罷了。 (2)第21行將位元組陣列內容轉換為字串。如果您將本範例的來源檔 設定為圖檔pic1.jpg,則轉換出來的字串我們並無法解讀(會是一連串 的亂碼),因為那是以jpg格式編碼的資料,而非文字資料,但您只 要更改複製目標檔的副檔名為jpg,就能用看圖軟體顯示該檔案格式 ,因為看圖軟體看得懂jpg格式編碼的資料。
位元串流的檔案處理 序列化(Serialization) 一般檔案與文字檔最大的差別在於 文字檔是所有純文字編輯器都可以輕鬆解讀的檔案,它可能使用 Unicode與ASCII或其他編碼方式,但無論如何,要解讀純文字檔是非 常容易的。 而一般檔案則只有熟悉該檔案編碼邏輯的程式設計師(或程式)才能 夠正確解讀,例如圖檔可以由看圖程式解讀 因此,非文字檔較具有保密性。所以,某些遊戲將遊戲進度等採用非文字 檔方式存放,這樣一來,不但可以於電源消失時保有資料,對於一般人或 編輯器而言,也無法解讀檔案內的資料。 這樣做的好處在於,遊戲資料不致被其他人快速破解而違背或喪失遊戲原 本的規劃。
位元串流的檔案處理 序列化(Serialization) 類似地,對於寫入非文字檔格式,Java提供了一種「序列化」 機制,它可以將物件完整寫入檔案中(採用位元串流方式), 等到其他程式需要時,則可以將之取出,而一般人若要直接閱 讀檔案,則無法理解檔案內的物件資料。 因此,當物件需要存放於磁碟或於網路中傳輸時,通常會使用序列化 技術來完成。
位元串流的檔案處理 序列化(Serialization) Java序列化功能的相關技術最主要的是Serializable介面,任何類 別只要實作Serializable介面就可以享有預設序列化功能。 而預設序列化則提供了最基本的完整寫入物件以及完整讀取物件的能 力。 至於更進階的序列化設計則超越本書介紹範圍,暫且不提。 雖然採用位元串流的序列化提供了非文字檔的一般保密性,但它並不是絕 對的保密,對於有經驗的Java程式設計師而言,仍舊可能讀取這些物件資 料 而預設的序列化寫入物件功能,只提供了完整寫入物件的功能(包含被宣 告為private的資料),如果您不想要某些私有資料因此被竊取,可以利用 transient關鍵字,強迫它在寫入物件時不被寫入,而由於未被寫入,因此 也就不會被讀取。
位元串流的檔案處理 序列化(Serialization) readObject()與writeObject()兩個函式是執行讀取與寫入物件的 函式,類別只要提供了這兩個函式,當進行序列化時,這兩個 函式就會自動發生作用。 而本章重點在於檔案的讀寫,對於位元串流而言,有兩個重要的類別 分別可以用來讀取及寫入物件,分別是ObjectInputStream與 ObjectOutputStream類別,而它們分別提供了readObject()與 writeObject()方法。
位元串流的檔案處理 序列化(Serialization) ObjectOutputStream類別與writeObject()方法 由圖12-3中,我們可以發現ObjectOutputStream類別繼承自 OutputStream類別,其建構子及writeObject()方法分別如下: ObjectInputStream類別與readObject()方法 由圖12-3中,我們可以發現ObjectInputStream類別繼承自InputStream 類別,其建構子及readObject()方法分別如下: ObjectOutputStream(OutputStream out) // ObjectOutputStream建構子 void writeObject(Obecjt obj) //寫入物件到位元串流 ObjectInputStream(InputStream in) // ObjectInputStream建構子 Object readObject() //由位元串流讀出物件並回傳
位元串流的檔案處理 序列化(Serialization) 接下來,我們直接透過兩個範例來理解物件序列化的功能,首 先我們將寫入物件,然後再由另一個範例讀取該物件。 【觀念範例12-10】:使用序列化寫入物件。 範例12-10:ch12_10.java(隨書光碟 myJava\ch12\ch12_10.java ) 1 2 3 4 5 6 7 8 9 10 11 /* 檔名:ch12_10.java 功能:序列化 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_10 //主類別 { public static void main(String args[]) throws IOException
位元串流的檔案處理 序列化(Serialization) 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 { FileOutputStream fo = new FileOutputStream("c:\\myJava\\ch12\\file\\file6.tmp"); ObjectOutputStream oos = new ObjectOutputStream(fo); oos.writeObject(new CMyStudent("S9703501","王大民",89,84,75)); oos.writeObject(new CMyStudent("S9703502","郭小志",77,69,87)); oos.writeObject(new CMyStudent("S9703503","胡小龍",65,68,77)); oos.close(); fo.close(); //關檔 } class CMyStudent implements Serializable //CMyStudent實作Serializable介面 private String id; private String name; private int scoreComputer; private int scoreMath; private int scoreEnglish; private int scoreSum; public CMyStudent(String str1,String str2,int i,int j,int k)
位元串流的檔案處理 序列化(Serialization) 執行結果(執行完畢產生file6.tmp檔) 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 { id=str1; name=str2; scoreComputer=i; scoreMath=j; scoreEnglish=k; computeSum(); } public void computeSum() scoreSum=scoreComputer+scoreMath+scoreEnglish; public void printSum() //空敘述
位元串流的檔案處理 序列化(Serialization) 範例說明: (1)第25~51行為自定的CMyStudent類別,包含資料、建構子、兩個成 員函式。其中printSum函式為空的敘述。 (2)第16~18行透過writeObject()函式將三個物件寫入位元串流,由於 未使用緩衝區,故會同時寫入file6.tmp檔案內。若直接透過純文字編 輯器觀看file6.tmp,會出現一些亂碼,如果使用UntraEdit等編輯器開 啟file6.tmp,則可以看見實際存在的位元資料,但解讀不易 若讀者有興趣,可以在網路上參閱「侯捷所著之Java的 物件永續之道」文章,當中有解析實際存入物件時的格 式細節。 不論如何,對於一般使用者而言,file6.tmp已經具備一些保密性。我 們將於下一個範例中,讀出這些物件。
位元串流的檔案處理 序列化(Serialization) 【觀念範例12-11】:使用序列化讀出物件。 範例12-11:ch12_11.java(隨書光碟 myJava\ch12\ch12_11.java ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /* 檔名:ch12_11.java 功能:序列化 */ package myJava.ch12; import java.lang.*; import java.io.*; public class ch12_11 //主類別 { public static void main(String args[]) throws IOException,ClassNotFoundException FileInputStream fi = new FileInputStream("c:\\myJava\\ch12\\file\\file6.tmp"); ObjectInputStream ois = new ObjectInputStream(fi); CMyStudent obj1;
位元串流的檔案處理 序列化(Serialization) 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 obj1=(CMyStudent)ois.readObject(); obj1.printSum(); ois.close(); fi.close(); //關檔 } class CMyStudent implements Serializable //CMyStudent實作Serializable介面 { private String id; private String name; private int scoreComputer; private int scoreMath; private int scoreEnglish; private int scoreSum; public CMyStudent(String str1,String str2,int i,int j,int k)
位元串流的檔案處理 序列化(Serialization) 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 id=str1; name=str2; scoreComputer=i; scoreMath=j; scoreEnglish=k; computeSum(); } public void computeSum() { scoreSum=scoreComputer+scoreMath+scoreEnglish; public void printSum() //修改函式內容,但函式不可或缺 System.out.println(id + " " + name + " 總分=" + scoreSum);
位元串流的檔案處理 序列化(Serialization) 執行結果: 範例說明: (1)由於程式中執行了readObject()函式,而它可能引發 ClassNotFoundException例外,故需要在main()函式後面加上例外丟出 的程式碼。 (2)第29~57行為自定的CMyStudent類別,和上一個範例僅有 printSum()的內容不同。 S9703501 王大民 總分=248 S9703502 郭小志 總分=233 S9703503 胡小龍 總分=210
位元串流的檔案處理 序列化(Serialization) 範例說明: (3)第17、19、21行透過readObject()函式將檔案(位元串流)內的三 個物件依序讀出,並在第18、20、22行執行printSum()方法,請注意 ,我們編譯及執行範例的順序為編譯ch12_10.java(此時會產生 ch12_10.class與CMyStudent.class)、執行ch12_10,接著在編譯 ch12_11.java(此時會產生ch12_11.class與CMyStudent.class,其中 CMyStudent.class將會覆蓋上一個範例的同一類別檔)、執行ch12_11 。故此時執行到printSum()時,會印出總分,如同執行結果。 (4)在這個範例中,我們可以得知,重新讀取物件後,可以重新撰寫 某個函式的內容,由於讀入的物件資料不包含函式的原有內容,因此 會執行新的函式內容。
位元串流的檔案處理 序列化(Serialization) 範例說明: (5)假設我們省略兩個範例中的任一欄位或任一函式,則當執行本範 例時將會發生執行時期的錯誤,原因是當存入物件時,會將所有欄位 都寫入,並且所有的函式也會寫入,但不包含函式內容。 (6)由這個範例可以發現,除非程式設計師知道原有物件的所有欄位 及函式,否則將無法正確讀出存放於檔案內的物件,因此具有保密性 。