六、Typical uses of I/O streams
/*
* @filename IOStreamDemo.java
* @author Bruce Eckel
*
* ToDo Typical I/O stream configurations.
*/
import java.io.EOFException;
import java.io.IOException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.StringReader;
public class IOStreamDemo {
// Throw exceptions to console:
public static void main(String[] args) throws IOException {
String s = null;
String s2 = null;
// 1. Reading input by lines:
BufferedReader in = new BufferedReader(
new FileReader("IOStreamDemo.java"));
while((s = in.readLine()) != null)
s2 += s + "\n";
in.close();
// 1b. Reading standard input:
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
System.out.print("Enter a line:");
System.out.println(stdin.readLine());
// 2. Input from memory
StringReader in2 = new StringReader(s2);
int c;
while((c = in2.read()) != -1)
System.out.print((char) c);
// 3. Formatted memory input
try {
DataInputStream in3 = new DataInputStream(
new ByteArrayInputStream(s2.getBytes()));
while(true)
System.out.print((char) in3.readByte());
} catch(EOFException e) {
System.err.println("End of Stream");
}
// 4. File output
try {
BufferedReader in4 = new BufferedReader(
new StringReader(s2));
PrintWriter out1 = new PrintWriter(
new BufferedWriter(new FileWriter("IODemo.out")));
int lineCount = 1;
while((s = in4.readLine()) != null)
out1.println(lineCount++ + ": " + s);
out1.close();
} catch(EOFException e) {
System.err.println("End of Stream");
}
// 5. Storing & recovering data
try {
DataOutputStream out2 = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("Data.txt")));
out2.writeDouble(3.14159);
out2.writeChars("That was pi\n");
out2.writeBytes("That was pi\n");
out2.close();
DataInputStream in5 = new DataInputStream(
new BufferedInputStream(new FileInputStream("Data.txt")));
BufferedReader in5br = new BufferedReader(
new InputStreamReader(in5));
// Must use DataInputStream for data
System.out.println(in5.readDouble());
// Can now use the "proper" readLine()
System.out.println(in5br.readLine());
// But the line comes out funny
// The one created with writeBytes is OK:
System.out.println(in5br.readLine());
} catch(EOFException e) {
System.err.println("End of Stream");
}
// 6. Reading / Writing random access files
RandomAccessFile rf = new RandomAccessFile("rtest.dat", "rw");
for(int i = 0; i < 10; i++)
rf.writeDouble(i * 1.414);
rf.close();
rf = new RandomAccessFile("rtest.dat", "rw");
rf.seek(5 * 8);
rf.writeDouble(47.0001);
rf.close();
rf = new RandomAccessFile("rtest.dat", "r");
for(int i = 0; i < 10; i++)
System.out.println("Value " + 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。
/*
* @filename IOProblem.java
* @author Bruce Eckel
*
* ToDO Java 1.1 and higher I/O Problem Deom.
*/
import java.io.IOException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
public class IOProblem {
// Throw exceptions to console:
public static void main(String[] args) throws IOException {
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("Data.txt")));
out.writeDouble(3.14159);
out.writeBytes("That was the value of pi\n");
out.writeBytes("This is pi/2:\n");
out.writeDouble(3.14159/2);
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream("Data.txt")));
BufferedReader inbr = new BufferedReader(
new InputStreamReader(in));
// The doubles written BEFORE the line of text
// read back correctly:
System.out.println(in.readDouble());
// Read the lines of text:
System.out.println(inbr.readLine());
System.out.println(inbr.readLine());
// Trying to read the doubles after the line
// produces an end-of-file exception:
System.out.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)。
/*
* @filename Echo.java
* @author Bruce Eckel
*
* ToDo How to read from standard input.
*/
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Echo {
public static void main(String[] args) throws IOException {
String s;
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
while((s = in readLine()).length() != 0)
System.out.println(s);
// An empty line will terminate the program.
}
}
(2)Changing System.out to PrintWriter
- System.out 是 PrintStream,而 PrintStream 則是一個 OutputStream。
- PrintWriter 有一個構造函數可以以 OutputStream 為參數,將 System.out 轉換為 PrintWriter。
/*
* @filename ChangeSystemOut.java
* @author Bruce Eckel
*
* ToDo Turn System.out into a PrintWriter.
*/
import java.io.PrintWriter;
public class ChangeSystemOut {
public static void main(String[] args) {
/* 此處不能使用 PrintWriter(OutputStream out) 而必須使用
PrintWriter(OutputStream out, boolean autoFlush)
並將 autoFlush 設置為 true 將自動清空緩衝的功能開啟
否則很可能會看不到輸出。 */
PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello, world");
}
}
(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。
/*
* @filename Redirecting.java
* @author Bruce Eckel
*
* ToDo Demostrates standard I/O redirection.
*/
import java.io.IOException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
public class Redirecting {
public static void main(String[] args) throws IOException {
BufferedInputStream in = new BufferedInputStream(
new FileInputStream("Redirecting.java"));
PrintStream out = new PrintStream(
new BufferedOutputStream(new FileOutputStream("test.out")));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
String s;
while((s = br.readLine()).length() != 0)
System.out.println(s);
out.close(); // Remember this!!!
}
}
八、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 數據而非多份相異數據進行壓縮。
/*
* @filename GZIPcompress.java
* @author Bruce Eckel
*
* ToDo Uses GZIP compression to compress a file
* whose name is passed on the command line
*
* 【注】本例混用了 char-oriented streams 和
* byte-oriented streams:in 用的是 Reader,
* 而 GZIPOutputStream 的 constructor 只接受
* OutputStream 而非 Writer。文件開啟後,
* GZIPInputStream 會被轉換為 Reader。
*/
import java.io.IOException;
import java.io.BufferedReader;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class GZIPcompress {
public static void main(String[] args) throws IOException {
// 1)壓縮文件
BufferedReader in = new BufferedReader(
new FileReader(args[0]));
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(new FileOutputStream("test.gz")));
System.out.println("Writing file");
int c;
while((c = in.read()) != -1)
out.write(c);
in.close();
out.close();
// 2)顯示壓縮文件內容
System.out.println("Reading file");
BufferedReader in2 = new BufferedReader(
new InputStreamReader(new GZIPInputStream(
new FileInputStream("test.gz"))));
String s;
while((s = in2.readLine()) != null)
System.out.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。
/*
* @filename Logon.java
* @author Bruce Eckel
*
* ToDo Demonstrates the "transient" keyword.
*/
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
class Logon implements Serializable {
private Date date = new Date();
private String username;
// transient 聲明的 password (值)在 serializing 時將不會被存儲
private transient String password;
Logon(String name, String pwd) {
username = name;
password = pwd;
}
public String toString() {
String pwd = (password == null) ? "(n/a)" : password;
return "logon info: \n " + "username: " + username
+ "\n date: " + date + "\n password: " + pwd;
}
public static void main(String[] args)
throws IOException, ClassNotFoundException {
Logon a = new Logon("Hulk", "myLittlePony");
System.out.println("logon a = " + a);
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("Logon.out"));
o.writeObject(a);
o.close();
// Delay:
int seconds = 5;
long t = System.currentTimeMillis() + seconds * 1000;
while(System.currentTimeMillis() < t)
;
// Now get them back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("Logon.out"));
System.out.println("Recovering object at " + new Date());
a = (Logon) in.readObject();
System.out.println("logon a = " + a);
}
}
- Externalizable 的替代方案
- 實現 Serializable interface 並加入 private 的 writeObject() 和 readObject(),它們會取代 serialization 缺省的行為。
// 必須遵循以下的 signatures:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;
- 調用 ObjectOutputStream.writeObject() 時,傳入的 Serializable 對象會被檢查以確認有無自己的 writeObject(),有則執行;同理於 readObject()。
- ObjectOutputStream.defaultWriteObject() 和 ObjectInputStream.defaultReadObject() 用於處理 non-static 和 non-transient 成員。
- 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 模式。