分类: Java
2008-11-23 10:16:27
上期介紹了基本的 I/O 觀念,以及 non-stream I/O 之後,本期要介紹 Java I/O Stream。篇幅較長,因為作者在每一個細節都講解得非常詳盡,相信各位可以從中獲益良多。
Java I/O Stream(串流)
Stream是串流的意思,Data Stream可翻譯成資料串流,亦即資料在資料來源端(Data Source)與資料目的端(Data Sink)之間流動的概念。就像溪流有著固定的源頭、流向地及水流方向。流動的水就好比資料,水的源頭是資料來源端(Data Source),水所流向的目的地就是資料目的端(Data Sink),資料傳輸則是具方向性的。每一條溪流的源頭與目的地都不同(例如目的地不一定是大海,有可能是湖泊或是其他溪流的分支…等),因此Data Stream也會有不同的來源端(Data Source)與目的端(Data Sink)。
1. Source & Sink Stream
Data Source Stream又稱為Input stream或Reader,用途是初始資料流。Data Source Sink又稱為Output stream或Writer,作用為接收資料流。Java支援二種Stream資料型態分別是byte與character。當傳輸的資料是Byte時,使用Input stream類別與Output stream類別;傳輸的資料若為Character,則用Reader類別與Writer類別。
|
【圖】Data Stream、Data Source與Data Sink之間的關係圖
‧Input Stream與Output Stream
Input Stream與Output Stream用來輸入與輸出byte資料串列(byte stream),Input Stream輸入標的可以是File、String、bytes array、pipe、internet與其他的stream;Output Stream的輸出標的則包括File、bytes array、pipe。InputStream類別與OutputStream類別分別繼承自Object。
InputStream以read()方法讀取單一byte資料或一連串bytes(a bytes array)資料。
|
|
‧Reader與Writer
Reader與Writer用來輸入與輸出character資料串列(character stream)。Reader類別與Writer類別也分別繼承自Object而來。
Read與Write關於讀取與寫入的方法:
Read讀取資料的方法是read()方法,可用來讀取單一character資料或者是一連串character(String)的資料。read()方法的傳回值若為-1,表示已到檔尾。
方法名稱 |
傳回值 |
說明 |
read() |
int |
從Reader物件中讀取一個字元,並傳回下一個字元。 |
read(char[] cbuf) | int | 從Reader物件中讀取些許字元填入cbuf中,並傳回已經讀取的字元數目。 |
read(char[] cbuf) | int | 在Reader物件中從off位置開始讀取len個字元資料並填入到cbuf中,並傳回已經讀取的字元數目。 |
方法名稱 |
傳回值 |
說明 |
write(char[] cbuf) |
void |
寫出一個名為cbuf的字元陣列cbuf。 |
void write(char[] cbuf, int off, int len |
void |
將cbuf中從第off位置開始,並寫出長度為len的字元資料 |
write(int c) | void | 寫出一個指定的字元(也就是c的16位元所表示的字元資料)。 |
write(String str) | void | 寫出一個字串(str)。 |
write(String str, int off, int len) | void | 將str中從第off位置開始,並寫出長度為len的字元資料。 |
2. Node Streams
Byte Streams |
Character Streams |
用途 |
FileInputStream FileOutputStream |
FileReader FileWriter |
讀取/寫入檔案中的資料 |
ByteArrayInputStream ByteArrayOutputStream |
CharArrayReader CharArrayWriter |
讀取記憶體中buffer內的資料/將資料寫入記憶體中的buffer(Array資料型別) |
StringReader StringWriter |
讀取記憶體中buffer內的資料/將資料寫入記憶體中的buffer(String資料型別) | |
PipedInputStream PipedOutputStream |
PipedReader PipedWriter |
Pipe(process或thread)。利用Piped Stream能讓執行緒(threads)之間能彼此溝通,在使用上的時候必需要成對使用(有input端也有output端)。 |
‧FileInputStream與FileOutputStream
FileInputStream建構子 |
說明 |
FileInputStream(File file) |
建立一個FileInputStream並指定一個File類別物件file。 |
FileInputStream(FileDescriptor fdObj) |
建立一個FileInputStream並指定一個FileDescriptor類別物件fdObj。 |
FileInputStream(String name) | 建立一個FileInputStream並給定檔案名稱name |
FileInputStream建構子 |
說明 |
FileInputStream(File file) |
建立一個FileInputStream並指定一個File類別物件file。 |
FileOutputStream(File file, Boolean append) |
建立一個FileOutputStream並指定一個File類別物件file,若append變數設定為true則會將欲加入的新文字接續在原始檔案中繼有文字之後(write to the end of the file),反之若設為false則會將原始檔案中的文字清除並以新增加的文字取代。 |
FileOutputStream(FileDescriptor fdObj) | 建立一個FileIOututStream並指定一個FileDescriptor類別物件fdObj |
FileOutputStream(String name) | 建立一個FileOutputStream並給定檔案名稱name |
FileOutputStream(String name, boolean append) | 建立一個FileOutputStream並給定檔案名稱name,若append變數設定為true則會將欲加入的新文字接續在原始檔案中繼有文字之後(write to the end of the file),反之若設為false則會將原始檔案中的文字清除並以新增加的文字取代。 |
1. import java.io.*; 2. public class Ex_FileOutputStream 3. { 4. public static void main(String[] args) throws Exception 5. { 6. String s; 7. s = "Java輸入與輸出(Java.io套裝模組)"; 8. byte[] newData = s.getBytes(); 9. System.out.println("將字串\"" + s + "\"寫到檔案”) 10. System.out.println("資料長度:" + newData.length + " bytes."); 11. FileOutputStream fo = new FileOutputStream("SampleFile2.txt", true); 12. fo.write(newData); //直接將byte[]寫入檔案 13. fo.close(); // 關閉檔案 14. } 15. } | |
執行結果: |
程式第7行利用getBytes()方法將s字串指派到newData變數(byte[]陣列)中。第11行利用FileOutputStream開啟SampleFile2.txt檔,並將第二個參數設定為True,使後來加入新增資料時可保存先前的資料不至於被覆蓋。第12行利用write()方法直接將newData寫入。
‧FileReader與FileWriterFileReader與FileWriter分別是InputStreamReader與OutputStreamWriter的子類別(sub-classes)。FileReader因繼承自InputStreamReader類別,所以能合法使用InputStreamReader類別中所提供的方法。
FileReader建構子
FileReader建構子 |
說明 |
FileReader(File file) | 建立一個FileReader物件,並指定一個File物件以供讀取。 |
FileReader(FileDescriptor fd) | 建立一個FileReader物件,並指定一個FileDescriptor以供讀取。 |
FileReader(String fileName) | 建立一個FileReader物件,並指定一個File檔名以供讀取。 |
FileReader建構子 |
說明 |
FileWriter(File file) | 建立一個FileWriter並指定一的File類別物件file。 |
FileWriter(FileDescriptor fd) | 建立一個FileWriter並指定一的FileDescriptor類別物件fd。 |
FileWriter(String fileName) | 建立一個FileWriter並給定檔案名稱 |
FileWriter(String fileName, boolean append) | 建立一個FileWriter指定一的File類別物件與boolean值append,當append=true時表示可以將新增資料加在原始資料後面,當append=false時表示新增的資料將覆蓋/取代原始資料。 |
FileWriter範例實作
本範例先利用FileWriter將字串寫到指定檔案,再利用FileReader依序讀出資料。
1. import java.io.*; 2. public class Ex_FileWriter 3. { 4. public static void main(String[] args) throws Exception 5. { 6. String s; 7. s = "Java輸入與輸出(Java.io套裝模組)"; 8. System.out.println("利用FileWriter將資料寫到檔案!"); 9. System.out.println("將字串\"" + s + "\"寫到檔案\n資料長度:" + 10. s.length() + " bytes."); 11. FileWriter fw = new FileWriter("SampleFile2.txt"); 12. fw.write(s, 0, s.length()); //直接將String寫入檔案 13. fw.close(); // 關閉檔案 14. System.out.println("\n利用FileWriter將資料從檔案裡讀出!"); 15. FileReader fr = new FileReader("SampleFile2.txt"); 16. char[] buf = new char[1]; 17. int total_chars; 18. total_chars = fr.read(buf); 19. System.out.println("讀取到的SampleFile2.txt檔案內容如下"); 20. System.out.println("----------------------------------"); 21. while (total_chars != -1) 22. { 23. System.out.print(new String(buf, 0, total_chars)); 24. total_chars = fr.read(buf); 25. } 26. fr.close(); 27. } 28. } | |
執行結果: |
程式第12行fw.write(s, 0, s.length());直接利用writer將String寫入,s.length為字串長度。
Byte Streams |
Character Streams |
用途 |
BufferedInputStream BufferedOutputStream |
BufferedReader BufferedWriter |
暫存資料串流,直接從記憶體中讀取資料以增加執行上的效能。 |
FilterInputStream FilterOutputStream |
FilterReader FilterWriter |
是用來轉換資料型態的(不過不是他自己做而是由他的子類別來實作),資料流Stream可以經過filter過濾來做資料轉換的動作,值得注意的是FilterInputStream、FilterOutputStream、FilterReader與FilterWriter實際上並沒有提供任何的功能,他的實作方法都是由子類別所提供。 |
InputStreamReader OutputStreamWriter |
可將byte的資料轉成character的資料型態,並且可以指定要用哪一種編碼方式來讀取資料。 | |
DataInputStream DataOutputStream |
利用DataInputStream可以讀取不同類型的資料。利用DataInputStream可以寫入不同類型的資料 | |
ObjectInputStream ObjectOutputStream |
物件序列化,把物件資訊紀錄到檔案中。 | |
LineNumberInputStream | LineNumberReader | 用來追蹤目前的行號,並利用getLineNumber()方法來取得行號(此類別於Java2中已經不建議被使用) |
PushbackInputStream | PushbackReader | 將目前所讀到的資料(byte或character)往後退一格,以便再次重新讀取資料(當作甚麼事都沒發生過一樣) |
PrintStream | PrintWriter | 將輸入的資料顯示於螢幕上,其資料輸入來源是Stream。 |
FileReader建構子 |
說明 |
FilterOutputStream(OutputStream out) | 藉由所傳入的OutputStream物件建立一個FilterOutputStream。注意:所傳入的out是指OutputStream下所有的子類別皆可傳入並建立FilterOutputStream物件。 |
‧FilterReader與FilterWriter
FilterReader建構子
FileReader建構子 |
說明 |
FilterReader(Reader in) | 藉由所傳入的Reader物件建立一個FilterReader。注意:FilterReader建構子的存取權限設定是protected。 |
FilterWriter建構子
FileReader建構子 |
說明 |
FilterWriter (Writer out) | 藉由所傳入的Writer物件建立一個Filter Writer。注意:Filter Writer建構子的存取權限設定也是protected。 |
‧DataInput與DataOutput介面
實作DataInput或DataOutput介面的類別將實作出這二個介面提供的所有方法,使其可以讀取或寫入各種型態的資料(資料類型為java primitive types與UTF-8 formate),而DataInputStream、DataOutputStream與RandomAccessFile也實作了此介面並提供了介面中所有的方法。
DataInput介面所提供的方法
DataInput類別介面提供了15種方法,除了skipBytes()方法外,DataInput提供的方法名稱全是以 “read”為字首。
方法名稱 |
傳回值 |
說明 |
readBoolean() |
boolean |
若讀取的byte(位元組)若為0則回傳值為false,反之則回傳true。 |
readByte() | byte | 讀取一個byte。 |
readChar() | char | 讀取一個character(字元)。 |
readDouble() | double | 讀取double數值(8-bytes)。 |
readFloat() | float | 讀取float數值(4-bytes)。 |
readInt() | int | 讀取int數值(4-bytes)。 |
readLong() | long | 讀取long數值(8-bytes)。 |
readShort() | short | 讀取short數值(2-bytes)。 |
readUnsignedByte() | int | 讀取1個Unsigned位元組資料,並傳回int型態的資料(0~255)。 |
readUnsignedShort() | int | 讀取Unsigned short資料,並傳回int型態的資料(0~65536)。 |
readUTF() | String | 讀取UFT-8格式的資料字串。 |
readFully(byte[] b) | void | 將從Stream中讀取到的資料放入b中(b為緩衝區位元陣列) |
readFully(byte[] b, int off, int, len) | void | 於Stream中讀取到的資料中從off位置開始讀取len個位元組資料並填入到b中。 |
readLine() | String | 讀取Stream中下一行的文字 |
skipBytes(int n) | int | 讀取第n個位置之後的位元組資料。 |
DataInputStream與RandomAccessFile類別必須實作DataInput類別介面中所有的方法。
‧DataOutput介面所提供的方法
DataOutput類別介面提供了14種方法,方法名稱全是以 “write”為字首且傳回值都是void(也就是沒有傳回值)。
方法名稱 |
傳回值 |
說明 |
write(byte[] b) |
void |
將緩衝區的位元組陣列資料b寫到輸出資料流output stream。 |
write(byte[] b, int off, int len) | void | 將緩衝區的位元組陣列資料b中從off位置開始寫入len個位元組資料到output stream。 |
write(int b) | void | 將int位元資料(8個低階位元- eight low-order)寫到output stream。請注意寫入的不是b的數值資料而是b的位元資料。 |
writeBoolean(Boolean b) | void | 將b的布林值寫入output stream。 |
writeByte(int v) | void | 將int位元資料(8個低階位元- eight low-order)寫到output stream。 |
writeBytes(String s) | void | 將s字串資料寫入output stream。 |
writeChar(int v) | void | 將int位元組資料寫到output stream。也就是寫入字元的資料型態。 |
writeChars(String s) | void | 依序將s字串中的每一個字元資料寫到output stream(每1個char是2個bytes) |
writeDouble(double v) | void | 將double數值資料寫入output stream(8-bytes)。 |
writeFloat(float f) | void | 將float數值資料寫入output stream(4-bytes)。 |
writeInt(int v) | void | 將int數值資料寫入output stream(4-bytes)。 |
writeLong(long v) | void | 將long數值資料寫入output stream(8-bytes)。 |
writeShort(int v) | void | 將short數值資料寫入output stream(2-bytes)。 |
writeUTF(String str) | void | 將str字串資料中的每一個字元用2個bytes寫到output stream,而每一個字元的編碼方式是:Java-modifirf UTF |
DataOutputStream與RandomAccessFile類別必須實作DataOutput類別介面中所有的方法。
‧Java I/O 資料串流鏈結
實作上撰寫Java I/O程式時,為求效能與彈性,通常不會只用單一I/O物件來存取Source與Sink端中的資料(例如只使用FileReader與FileWriter),而會搭配不同I/O物件個別特性與彼此間的關係來達到目的,這種使用多種I/O物件之間彼此資料交換的行為我們稱之為 “資料串流鏈結”,同樣的也分為2種鏈結模式分別是InputStream鏈結模式與OutputStream鏈結模式。
InputStream鏈結模式:將資料自來源端接收後傳入記憶體中;
DataSource(Source) a Memory/Program(Sink)
OutputStream鏈結模式:將資料自來源端接收後傳入記憶體中;
Memory/Program(Source) a DataSink(Sink)
有關NodeStream與ProcessStream需求量的多寡會依照實際開發的需求而有所不同。
‧BufferedReader與BufferedWriter
利用資料緩衝區(Buffer)可大量降低檔案操作上的次數以增加程式執行上的效率。BufferedReader與Bufferedwriter是字元導向(char-oriented),讀取資料時不需逐字讀取,可利用readLine()作逐行讀取以提升資料讀取的效率。另外還有一組BufferedInputStream與BufferedOutputStream是位元導向(byte-oriented),資料讀取大致上與BufferedReader與Bufferedwriter相同,通常在input是一個stream且output也是一個stream時才比較容易使用到。以下針對BufferedReader與Bufferedwriter作詳盡說明。
BufferedReader建構子
FileReader建構子 |
說明 |
BufferedReader(Reader in) | 建立一個BufferReader物件,其參數in必須屬於Reader物件 |
BufferedReader(Reader in, int sz) | 建立一個BufferReader物件,其參數in必須屬於Reader物件。sz為設定buffer大小的參數,一般來說sz都會大於0,這樣的設定才有意義;若sz<=0系統便會丟出IllegalArgumentException例外 |
BufferedReader常用方法
|
BufferedWriter建構子
BufferedWriter建構子 |
說明 |
BufferedWriter(Writer out) | 建立一個BufferWriter物件,其參數out須屬於Writer物件 |
BufferedReader(Reader in, int sz)BufferedWriter(Writer out, int sz) | 建立一個BufferWriter物件,其參數out必須屬於Reader物件,sz為設定buffer大小的參數 |
‧BufferedWriter常用方法
|
BufferedReader與BufferedWriter範例程式
1. import java.io.*; 2. public class Ex_BufferedRW 3. { 4. public static void main(String[] args) throws Exception 5. { 6. // 利用BufferedReader讀取SampleFile.txt的原始資料 7. FileReader fr1 = new FileReader("SampleFile.txt"); 8. BufferedReader buf_fr1 = new BufferedReader(fr1); 9. String strReadLine; 10. System.out.println("利用BufferedReader讀取SampleFile.txt的原始資料如下"); 11. System.out.println("----------------------------------"); 12. strReadLine = buf_fr1.readLine(); // 一次讀取一行 13. while (strReadLine != null) // 判斷是否有抓到資料 14. { 15. System.out.println(strReadLine); 16. strReadLine = buf_fr1.readLine(); 17. } 18. buf_fr1.close(); //只要關閉Buffer物件即可,因為其他與之相關聯的資源也會一起被關閉 19. // 利用BufferedWriter將指定字串寫到SampleFile.txt 20. int i = 0; 21. String[] strWriteLine = new String[2]; 22. strWriteLine[0] = "Howard Tuan"; 23. strWriteLine[1] = "Baby Tuan"; 24. FileWriter fw = new FileWriter("SampleFile.txt", true); 25. BufferedWriter buf_fw = new BufferedWriter(fw); 26. for(i=0;i 27. { 28. buf_fw.write(strWriteLine[i], 0, strWriteLine[i].length()); 29. buf_fw.newLine(); 30. } 31. buf_fw.close(); 32. // 再利用BufferedReader讀取修改過後SampleFile.txt的資料 33. FileReader fr2 = new FileReader("SampleFile.txt"); 34. BufferedReader buf_fr2 = new BufferedReader(fr2); 35. System.out.println("\n再利用BufferedReader讀取修改過後的資料如下 "); 36. System.out.println("----------------------------------"); 37. strReadLine = buf_fr2.readLine(); // 一次讀取一行 38. while (strReadLine != null) // 判斷是否有抓到資料 39. { 40. System.out.println(strReadLine); 41. strReadLine = buf_fr2.readLine(); 42. } 43. buf_fr2.close(); 44. } 45. } | |
執行結果: |
4. RandomAccessFile
RandomAccessFile是唯一可以同時讀取(r)與寫入(rw)資料的類別,並且可利用指標(這裡所說的指標是檔案指標file pointer)來指定所要讀取或寫入的位置,若開啟的檔案不存在,則系統會自動建立一個新檔,其檔案指標會指向0的位置(預設為0)。
BufferedWriter建構子 |
說明 |
RandomAccessFile(File file, String mode) | 根據所指定的file物件來建立一個RandomAccessFile物件,並設定其存取模式,若mode = r表示可讀不可寫(唯讀),mode = rw則表示可讀可寫。 |
RandomAccessFile(String name, String mode) | 根據所指定的檔案名稱name來建立一個RandomAccessFile物件,並設定其存取模式。 |
|
1. import java.io.*; 2. public class Ex_RandomAccessFile 3. { 4. public static void main(String[] args) throws Exception 5. { 6. String s; 7. s = "Java SCJP\nJDK 1.3.1"; 8. byte[] newData = s.getBytes(); 9. RandomAccessFile rf1 = new RandomAccessFile("SampleFile.txt", "rw"); 10. System.out.println("利用RandomAccessFile將資料寫到檔案!"); 11. System.out.println("將字串\"" + s + "\"寫到檔案\n資料長度:" + 12. s.length() + " bytes."); 13. rf1.write(newData); 14. rf1.close(); 15. RandomAccessFile rf2 = new RandomAccessFile("SampleFile.txt", "r"); 16. System.out.println("\n利用RandomAccessFile讀取到的" + 17. "SampleFile.txt檔案內容如下"); 18. System.out.println("----------------------------------"); 19. while ((s = rf2.readLine()) != null ) 20. System.out.println(s); 21. rf2.close(); 22. } 23. } | |
執行結果: 利用RandomAccessFile將資料寫到檔案! 將字串"Java SCJP JDK 1.3.1"寫到檔案 資料長度:19 bytes. 利用RandomAccessFile讀取到的SampleFile.txt檔案內容如下 ---------------------------------- Java SCJP JDK 1.3.1 |
1. import java.io.*; 2. public class Ex_RandomAccessFile2 3. { 4. public static void main(String[] args) throws Exception 5. { 6. String s; 7. RandomAccessFile rf = new RandomAccessFile("Buffer.txt", "rw"); 8. // RandomAccessFile物件建立之初時的指標位置 9. System.out.println("利用getFilePointer()方法傳回目前的檔案指標位置:" + 10. // -- Insert Data 11. rf.writeUTF("My name is "); 12. long fpointer = rf.getFilePointer(); 13. System.out.println("加入UTF字串(\"My name is \")後的檔案指標位置:" + fpointer); 14. rf.writeUTF("Vincent"); 15. System.out.println("加入UTF字串(\"Vincent\")後的檔案指標位置:" + 16. rf.seek(0); 17. System.out.println("將所加入的2組UTF字串資料秀出:" + rf.readUTF() + 18. // -- Modify Data 19. rf.seek(fpointer); 20. System.out.println("\n將指標指向" + fpointer + "並加入UTF字串(\"Anita\")"); 21. rf.writeUTF("Anita"); 22. rf.seek(0); 23. System.out.println("最後將變更後的UTF字串資料秀出:" + rf.readUTF() + 24. rf.close(); 25. } 26. } | |
執行結果: 利用getFilePointer()方法傳回目前的檔案指標位置:0 加入UTF字串("My name is ")後的檔案指標位置:13 加入UTF字串("Vincent")後的檔案指標位置:22 將所加入的2組UTF字串資料秀出:My name is Vincent 將指標指向13並加入UTF字串("Anita") 最後將變更後的UTF字串資料秀出:My name is Anita |
1. … // Block of code 2. … 3. FileOutputStream fo = new FileOutputStream (“sample.txt”); 4. OutputStreamWriter OutWriter = new OutputStreamWriter (fo, “ISO2022CN”); 5. … 6. … // Block of code |
|