六、Typical uses of I/O streams
java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; IOStreamDemo { main([] args) { s = ; s2 = ; in = ( ("IOStreamDemo.java")); ((s = in.readLine()) != ) s2 += s + "\n"; in.close(); stdin = ( ()); ..print(); ..println(stdin.readLine()); in2 = (s2); c; ((c = in2.read()) != -1) ..print(() c); { in3 = ( (s2.getBytes())); () ..print(() in3.readByte()); } ( e) { ..println(); } { in4 = ( (s2)); out1 = ( ( ())); lineCount = 1; ((s = in4.readLine()) != ) out1.println(lineCount++ + + s); out1.close(); } ( e) { ..println(); } { out2 = ( ( ())); out2.writeDouble(3.14159); out2.writeChars(); out2.writeBytes(); out2.close(); in5 = ( ( ())); in5br = ( (in5)); ..println(in5.readDouble()); ..println(in5br.readLine()); ..println(in5br.readLine()); } ( e) { ..println(); } rf = (, ); ( i = 0; i < 10; i++) rf.writeDouble(i * 1.414); rf.close(); rf = (, ); rf.seek(5 * 8); rf.writeDouble(47.0001); rf.close(); rf = (, ); ( i = 0; i < 10; i++) ..println( + i + + rf.readDouble()); rf.close(); } }
(1)Input streams
- Buffered input file
- 需要開啟一個文件用於字符的讀取時,可以使用 FileReader 並以一個 String 或 File 對象指定文件名。基於速度考量,通常會把這個 FileReader 的 reference 傳給 BufferedReader 的 constructor 使這個文件具備緩衝功能。
- BufferedReader 的 readLine() 函數會將換行符過濾掉,所以使用它的時候必須自行添加換行符。
- java.lang.System 的三個 I/O 成員(final static):
- err:PrintStream
- out:InputStream
- in:PrintStream
- Input from memory
- 通過 String(已含內容)產生 StringReader。
- java.io.Reader(abstract class)的 read() 函數返回的是 int,所以使用 read() 時必須根據實際進行轉型。
- Formatted memory input
- 讀取格式化數據通常使用的是 DataInputStream(byte-oriented),因此也就只能使用 InputStream classes 而不能使用 Reader classes。
- 任何一個 Byte 對 DataInputStream 的 readByte() 函數來說都是合法的結果,所以不能憑它的返回值來判斷輸入是否結束。
- DataInputStream 的 available()(繼承自 java.io.FilterInputStream)函數返回可供讀取的字符數。但它的運作方式取決於所輸入的對象,所以應當結合實際使用。
- File output
- 產生一個 FileWriter 對象連接至文件,將其 reference 傳給 BufferedWriter 的 constructor(緩衝可以大幅提高 I/O 效能)。如果需要格式化輸出,可以再將 BufferedWriter 的 object reference 傳給 PrintWriter 的 constructor。(這樣產生的文件是文本文件)
- 使用 Buffered 類的 classes 緩衝輸出文件後,必須調用 close() 函數關閉文件,否則緩衝區的內容可能不會被清空從而得不到完整的結果。
(2)Output streams
- Storing and recovering data
- output streams 主要分為兩種:1)為了讓人直接查看結果(如 java.io.PrintWriter);2)為了讓 DataInputStream 可以再次讀取(Random Access File 不屬于上述的兩種,但其數據格式與 DataInputStream 和 DataOutputStream 相容)。
- DataOutputStream 使用 writeChars() 和 writeBytes() 這兩個函數輸出字符串。其中 writeChars() 是以 16bit 的 Unicode 字符進行輸出的,所以如果用 readLine() 讀取這些文本,那麽每個字符間都會多了一個空格,這是因為 Unicode 會額外插入一個 byte。因此,對 ASCII 而言,使用 writeBytes() 加上換行符輸出,然後以 readLine() 讀取的方法會更為簡單。
- Random access files
- java.io.RandomAccessFile 完全獨立於 I/O 繼承體系之外。由於它實現了 DataInput 和 DataOutput 接口,所以它無法和 InputStream / OutputStream 的子類搭配使用(例如,無法為其加上緩衝功能)。
(3)A bug?
- 數據的寫入必須出現在文本之前,否則在讀取的時候就會擲出 EOFException。
java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; IOProblem { main([] args) { out = ( ( ())); out.writeDouble(3.14159); out.writeBytes(); out.writeBytes(); out.writeDouble(3.14159/2); out.close(); in = ( ( ())); inbr = ( (in)); ..println(in.readDouble()); ..println(inbr.readLine()); ..println(inbr.readLine()); ..println(in.readDouble()); } }
3.14159 That was the value of pi This is pi/2: Exception in thread "main" java.io.EOFException at java.io.DataInputStream.readFully(Unknown Source) at java.io.DataInputStream.readLong(Unknown Source) at java.io.DataInputStream.readDouble(Unknown Source) at IOProblem.main(IOProblem.java:42)
(4)Piped streams
- PipedInputStream / PipedOutputStream / PipedReader / PipedWriter 主要用於 multithreads。
七、Standard I/O
- Standard I/O:可為程序所用的單一信息流。所有的程序輸入皆可取自 standard input,所有的輸出皆可送至 standard output;所有的錯誤信息皆可送至 standard error。
(1)Reading from standard input
- System.out 和 System.err 都被包裝為 PrintStream object(in 和 err 是 java.lang.System 的 final static PrintStream 成員),可以直接使用;而 System.in 則是原始的 InputStream,所以在讀取前必須加以包裝(通常是 InputStreamReader + BufferedReader)。
java.io.; java.io.; java.io.; Echo { main([] args) { s; in = ( (.)); ((s = in readLine()).length() != 0) ..println(s); } }
(2)Changing System.out to PrintWriter
- System.out 是 PrintStream,而 PrintStream 則是一個 OutputStream。
- PrintWriter 有一個構造函數可以以 OutputStream 為參數,將 System.out 轉換為 PrintWriter。
java.io.; ChangeSystemOut { main([] args) { out = (., ); out.println(); } }
(3)Redirecting standard I/O
- java.io.System 提供了幾個 static 函數,可對 standard input、standard output、standard error 等 I/O streams 進行重定向:
- setIn(InputStream)
- setOut(PrintStream)
- setErr(PrintStream)
- I/O 重定向處理的是以 byte 而非 character 為單位的 streams,因此使用的是 InputStream 和 OutputStream 而不是 Reader 和 Writer。
java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; Redirecting { main([] args) { in = ( ()); out = ( ( ())); .setIn(in); .setOut(out); .setErr(out); br = ( ()); s; ((s = br.readLine()).length() != 0) ..println(s); .close(); } }
八、Compression
- Java I/O library 提供了一系列的 classes 讓我們可以以壓縮格式對 streams 進行讀寫,這些 classes 將既有的 I/O classes 包裝起來並提供壓縮功能。
- Java I/O 的 compression library 全部繼承自 InputStream / OutputStream,這是因為它們處理的是 bytes 而不是 characters。
compression class | function |
java.util.zip.CheckedInputStream | getCheckSum() 能針對任意類型的 InputStream 產生 checksum(校驗碼)而不僅是解壓 |
java.util.zip.CheckedOutputStream | getCheckSum() 能針對任意類型的 OutputStream 產生 checksum(校驗碼)而不僅是壓縮 |
java.util.zip.DeflaterOutputStream | compression classes 的 base class |
java.util.zip.ZipOutputStream | 可將數據壓縮為 Zip 格式 |
java.util.zip.GZIPOutputStream | 可將數據壓縮為 GZIP 格式 |
java.util.zip.InflaterInputStream | decompression classes 的 base class |
java.util.zip.ZipInputStream | 可將以 Zip 格式存儲的數據解壓 |
java.util.zip.GZIPInputStream | 可將以 GZIP 格式存儲的數據解壓 |
(1)Simple compression with GZIP
- compression 相關 classes 的運用方式很直觀:只需將 output stream 包裝成 GZIPOutputStream / ZipOutputStream,並將 input stream 包裝成 GZIPInputStream / ZipInputStream,然後直接進行一般的 I/O 處理即可。
- GZIP 接口較為簡單因此比較適合對單一的 stream 數據而非多份相異數據進行壓縮。
java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.util.zip.; java.util.zip.; GZIPcompress { main([] args) { in = ( (args[0])); out = ( ( ())); ..println(); c; ((c = in.read()) != -1) out.write(c); in.close(); out.close(); ..println(); in2 = ( ( ( ()))); s; ((s = in2.readLine()) != ) ..println(s); } }
(2)Multifile storage with Zip
- java.util.zip 提供了一系列針對 Zip 格式的 classes;其中 CheckSum(interface)有兩類:Adler32(較快) 和 CRC32(較慢但更為精確)。
(3)Java ARchives (JARs)
- JAR 是由多個經過 Zip 壓縮的文件所組成的單一文件,它同時附有一份內部文件的清單(manifest)。其中 mainfest 可以自己建立,否則 JAR 程序會自動產生。
- Sun JDK 內附了一個 JAR 工具,其壓縮格式如下:
jar [options] destination [mainfest] inputfile(s)
option | function |
c | 創建新的文件 |
t | 列出文件的內容 |
x | 解壓所有文件 |
u | 更新既有的文件 |
v | 產生 jar 執行過程的完整信息 |
f | 指定歸檔的文件名。如果不進行指定,JAR 就會假設其輸入來自 standard input,並在建立文件時假定其輸出對象為 standard output |
m | 指定 manifest 文件名 |
O | 只存儲文件,並不進行 Zip 壓縮 |
M | 通知 JAR 不要自動產生 manifest 文件 |
i | 為指定的 JAR 文件生成索引信息 |
C | 指定歸檔的目錄(包含其子目錄) |
九、Object serialization
- Java 的 object serialization 機制可以將任何實現了 Serializable interface 的對象轉換為 bytes 序列,該序列可被還原為原來的對象,還可以通過網絡進行傳輸(object serialization 機制會自動處理 bytes 序列在不同 OS 上的差異)。
- Object serialization 可將 serializable 對象寫入磁盤,並於程序重新啟動時恢復其狀態;從而實現了 lightweight persistence(之所以稱其為 lightweight,是因為 serialize 和 deserialize 的動作需要手動完成)。
- Object serialization 使 Java 得以支持 RMI(Remote Method Invocation)和 JavaBeans。
- 對一個對象進行 serialization,只需它實現了 Serializable interface 即可;而 Serializable interface 本身並不具備任何的函數(這樣的 interface 也稱為 marker interface)。
- Object Serialization:
- serialize:產生某種 OutputStream 對象並以 ObjectOutputStream 對象加以包裝,然後調用 writeObject() 即可,輸出的數據將被送回該 OutputStream 中;
- deserialize:產生某種 InputStream 對象並以 ObjectInputStream 對象加以包裝,然後調用 readObject() 即可(readObject() 返回的是經過 upcasting 的對象,所以使用該對象前必須進行 downcasting)。
- Object serialization 不僅存儲對象在內存中的原始數據,還會追蹤該對象內含的 reference 所指的對象並將它們存儲,以此類推,整個對象網絡都會被存儲下來。
- Deserializing 並不會調用任何的 constructor(包括 defalut constructor ),整個對象的狀態都是通過讀取 InputStream 恢復的。
(1)Finding the class
- Deserializing 需要通過對象原始的 .class 文件,所以進行 deserializing 時必須首先確保 JVM 可以找到( local 的 classpath 或是 Internet )相應的 .class 文件。
(2)Controlling serialization
- 與 Serializable interface 相比,Externalizable interface(繼承自 Serializable interface )增加了 writeExternal() 和 readExternal() 兩個函數,用以控制 serialization 的過程。
- 與 Serializable interface 不同,使用 Externalizable interface 進行 deserializing 時,所有的 default constructor 都會被調用( Serializable interface 僅讀取 InputStream,並不會有任何的調用動作),然後才是 readExternal()。由於 Externalizable interface 的這個特點,因此在使用它的時候必須在 writeExternal() 和 readExternal() 中根據實際調用正確的 constructor。
- The transient keyword
- Serializtion 會略過由關鍵字 transient 聲明的數據(不進行存儲)。
- 因為缺省情況下 Externalizable 對象的值並不會被存儲,所以 transient 只用於 Serializable。
java.io.; java.io.; java.io.; java.io.; java.io.; java.io.; java.util.; Logon { date = Date(); username; password; Logon( name, pwd) { username = name; password = pwd; } toString() { pwd = (password == null) ? : password; + + username + + date + + pwd; } main([] args) , { Logon a = Logon(, ); ..println( + a); o = ( ()); o.writeObject(a); o.close(); seconds = 5; t = .currentTimeMillis() + seconds * 1000; (.currentTimeMillis() < t) ; in = ( ()); ..println( + Date()); a = (Logon) in.readObject(); ..println( + a); } }
- Externalizable 的替代方案
- 實現 Serializable interface 並加入 private 的 writeObject() 和 readObject(),它們會取代 serialization 缺省的行為。
writeObject( stream) ; readObject( stream) , ;
- 調用 ObjectOutputStream.writeObject() 時,傳入的 Serializable 對象會被檢查以確認有無自己的 writeObject(),有則執行;同理於 readObject()。
- ObjectOutputStream.defaultWriteObject() 和 ObjectInputStream.defaultReadObject() 用於處理 non-static 和 non-transient 成員。
- 實現 Serializable interface 並加入 private 的 writeObject() 和 readObject(),它們會取代 serialization 缺省的行為。
- Versioning:Java 的 versioning 機制過於簡單,並不適用於所用的場合(詳見 JDK 的 HTML 說明文檔)。
(3)Using persistence
- Serialization 機制可以完全恢復單一 stream 中的對象網絡,並且不會額外的複製任何對象,也就是說單一 stream 中如果有多個對象擁有指向同一對象的 reference,該 reference 指向的對象僅會保存一份。對於非單一的 stream,由於系統無法知道各個 stream 中的對象其實是相同的,所以系統會製造出完全不同的對象網絡。
- 由於 static 成員並不屬於對象,所以 serializing 並不存儲 static 成員,因此需要自行提供 static 函數對其進行處理。
十、Tokenizing input
- Tokenizing:将 character 序列劃分為 token 序列(由選定的分隔符劃分而成的文本片段)。
(1)StreamTokenizer
- java.io.StreamTokenizer 並非繼承自 InputStream / OutputStream,但因為它只能處理 InputStream(Deprecated)和 Reader 對象,所以被歸類於 I/O。
(2)StringTokenizer
- java.util.StringTokenizer 一般只用於處理簡單的應用( String ),可被視為簡化的 java.io.StreamTokenizer。
(3)Checking capitalization style
【Summary】
- Java I/O 體系主要可分為 bytes-oriented 和 chars-oriented,應根據實際進行選擇,一般情況下支持 Unicode 的 chars-oriented classes 應當被優先考慮。
- Java I/O library 中大量使用了 decorator 模式。