1、基本的輸入流和輸出流<br />流是Java 中最重要的基本概念之一。文件讀寫、網絡收發、進程通信,幾乎所有需要輸入輸出的地方,都要用到流。
流是做什麼用的呢?就是做輸入輸出用的。為什麼輸入輸出要用“流”這種方式呢?因為程序輸入輸出的基本單位是字節,輸入就是獲取一串字節,輸出就是發送一串字節。但是很多情況下,程序不可能接收所有的字節之後再進行處理,而是接收一點處理一點。比方你下載魔獸世界,不可能全部下載到內存裡再保存到硬盤上,而是下載一點就保存一點。這時,流這種方式就非常適合。
在Java 中,每個流都是一個對象。流分為兩種:輸入流(InputStream)和輸出流(OutputStream)。對於輸入流,你只要從流當中不停地把字節取出來就是了;而對於輸出流,你只要把準備好的字節串傳給它就行。
流對像是怎麼獲得的呢?不同的外部系統,獲取流的方式也不同。例如,文件讀寫就要創建FileInputStream/FileOutputStream 對象,而網絡通信是通過Socket 對象來獲取輸入輸出流的。一般來說,如果一個類有getInputStream() 或getOutputStream() 這樣的方法,就表明它是通過流對象來進行輸入輸出的。
InputStream 是輸入流,下面是一個通過InputStream 讀取文件的例子:
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.FileNotFoundException; import java.util.Arrays; /** * 通過流讀取文件*/ public class ReadFileDemo { // 程序入口public static void main(String[] args) { String path = "c:/boot.ini"; File file = new File(path); // 創建輸入流InputStream is ; try { is = new FileInputStream(file); } catch (FileNotFoundException e) { System.err.println("文件" + path + " 不存在。"); return; } // 開始讀取byte[] content = new byte[0]; // 保存讀取出來的文件內容byte[] buffer = new byte[10240]; // 定義緩存try { int eachTime = is.read(buffer); // 第一次讀取。如果返回值為-1 就表示沒有內容可讀了。 while (eachTime != -1) { // 讀取出來的內容放在buffer 中,現在將其合併到content。 content = concatByteArrays(content, buffer, eachTime); eachTime = is.read(buffer); // 繼續讀取} } catch (IOException e) { System.err.println("讀取文件內容失敗。"); e .printStackTrace(); } finally { try { is.close(); } catch (IOException e) { // 這裡的異常可以忽略不處理} } // 輸出文件內容String contentStr = new String(content); System. out.println(contentStr); } /** * 合併兩個字節串* * @param bytes1 字節串1 * @param bytes2 字節串2 * @param sizeOfBytes2 需要從bytes2 中取出的長度* * @return bytes1 和bytes2 中的前sizeOfBytes2 個字節合併後的結果*/ private static byte[] concatByteArrays(byte[] bytes1, byte[] bytes2, int sizeOfBytes2) { byte[] result = Arrays.copyOf(bytes1, (bytes1 .length + sizeOfBytes2)); System.arraycopy(bytes2, 0, result, bytes1.length, sizeOfBytes2); return result; } }
雖然寫得很嗦,但這確實是InputStream 的基本用法。注意,這只是一個例子,說明如何從輸入流中讀取字節串。實際上,Java 提供更簡單的方式來讀取文本文件。以後將會介紹。
相比從流中讀取,使用OutputStream 輸出則是非常簡單的事情。下面是一個例子:
import java.io.OutputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; import java.util.Date; /** * 將當前日期保存到文件*/ public class SaveFileDemo { public static void main(String[] args) throws IOException { String path = "c:/now.txt"; File file = new File(path); if (!file.exists() && !file.createNewFile() ) { System.err.println("無法創建文件。"); return; } OutputStream os = new FileOutputStream(file); // 創建輸出流(前提是文件存在) os.write(new Date().toString( ).getBytes()); // 將當前時間寫入文件os.close(); // 必須關閉流,內容才會寫入文件。 System.out.println("文件寫入完成。"); } }
Java 還提供其它的流操作方式,但它們都是對InputStream 和OutputStream 進行擴展或包裝。所以這兩個類是基礎,必須要理解它們的使用。
2、Reader 和Writer
介紹了InputStream 和OutputStream,接下來介紹Reader 和Writer。這兩個類其實就是將InputStream 和OutputStream 包裝了一下。不過它們處理的不是字節(byte),而是字符(char)。如果一個流當中的內容都是文本,那麼用Reader/Writer 處理起來會簡單些。下面是一個用Reader 讀取文本文件的例子:
import java.io.FileReader; import java.io.IOException; import java.io.Reader; /** * 讀取文本文件*/ public class ReadTextFileDemo { // 程序入口public static void main(String[] args) { String path = "c:/boot.ini"; String content = ""; try { Reader reader = new FileReader(path); char[] buffer = new char[10240]; int count; while ((count = reader. read(buffer)) != -1) { content += new String(buffer, 0, count); } } catch (IOException e) { System.err.println("讀取文件失敗。"); e.printStackTrace (); } System.out.println(content); } }
至於如何用Writer 將文本內容寫入文件,這裡就不給出例子了,看官自己試著寫一下吧。
上面這個例子,仍然不是讀取文本文件最常用的方式。 Java 提供BufferedReader,我們通常用它來讀取文本文件。下面是一個例子:
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * 用BufferedReader 讀取文本文件*/ public class ReadTextDemo2 { public static void main(String[] args) { String path = "c:/boot.ini"; String content = ""; try { BufferedReader reader = new BufferedReader(new FileReader(path)); String line; while ((line = reader.readLine()) != null) { content += line + "/n"; } } catch (IOException e) { System.err.println("讀取文件失敗。"); e.printStackTrace(); } System.out.println(content); } }
3、對象序列化<br />對象序列化也是流應用的一個重要方面。序列化就是把一個對象轉換成一串字節,既可以保存起來,也可以傳給另外的Java 程序使用。 ObjectOutputStream 和ObjectInputStream 就是專門用來進行序列化和反序列化的。下面就是一個簡單的例子:
import java.io.ObjectOutputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.io.ObjectInputStream; import java.io.FileInputStream; import java.io.EOFException; /** * ObjectOutputStream/ObjectInputStream 示例。 * 這兩個類分別用於序列化和反序列化。 */ public class SerializationDemo { public static void main(String[] args) throws Exception { String path = "c:/persons.data"; File f = new File(path); if (!f.exists()) { f.createNewFile(); } writePersons(path); readPersons(path); } // 從指定的文件中讀取Person 對象private static void readPersons(String path) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream (path)); Person p; while (true) { try { p = (Person) ois.readObject(); System.out.println(p); } catch (EOFException e) { break; } } } // 將Person 對象保存到指定的文件中private static void writePersons(String path) throws IOException { Person[] persons = new Person[]{ new Person("張三", 23), new Person("李四", 24) }; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path)); for (Person person : persons) { oos.writeObject(person); } oos.close(); } //////////// /////////////////////////////////////////////// private static class Person implements Serializable { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '/'' + ", age=" + age + '}'; } } }
這個例子裡面看不到字節和字符的讀寫,因為這兩個類都包裝好了。上面只是一個簡單的例子,序列化要寫好的話還是有不少講究的。想深入了解序列化,可以看看這裡。本文只關注跟流有關的部分。其實序列化用的很少,因為序列化降低了靈活性,所以可以不用的話一般都不會用。