由於Java是一種完全物件導向的進階語言,所以在編寫程式的時候資料大都存放在對象當中。我們有時會需要將記憶體中的整個對象都寫入到檔案中去,然後在適當的時候再從檔案中將對象還原至記憶體。我們可以使用java.io.ObjectInputStream和java.io.ObjectOutputStream類來完成這個任務。
1、對象的序列化(Serialize)
序列化是指將對象的狀態資訊轉換為可以儲存或傳輸的形式的過程。在序列化期間,對象將其目前狀態寫入到臨時或持久性儲存區。以後,可以通過從儲存區中讀取或還原序列化對象的狀態,重新建立該對象。
2、ObjectInputStream類 和ObjectOutputStream類
如果我們想要序列化一個對象,如我們自訂的User類的對象,那麼這個對象必須實現Serializable介面。Serializable介面沒有任何的抽象方法,實現這個介面僅僅是為了通知編譯器這個對象將要被序列化而已。類似的用法還有Cloneable介面,實現這個介面也只是起到通知編譯器的作用。
為了示範如何進行對象的序列化,我們先設計一個User類:
package cls;import java.io.*;public class User implements Serializable // 實現Serializable介面,僅僅直到標識這個類可被序列化的作用{ // 可序列化對象的版本 private static final long serialVersionUID = 1L; private String name; private int num; public User(String name,int num) { this.name = name; this.num = num; } public String getName() { return name; } public int getNum() { return num; }}
注意,seriaVersionUID是指可序列化對象的版本。如果我們沒有指定這個版本,那麼編譯器為自動為實現Serializable介面的類產生一個seriaVersionUID。如果是自動產生的,那麼一次如果更改了User類,則自動產生的seriaVersionUID就會不同。在從檔案中讀回對象時,如果兩個對象的seriaVersionUID不同,就會拋出java.io.InvalidClassException。因此如果我們今後不需要改動User類的話,最好自己指定seriaVersionUID,以防止發生該異常。
下面我們使用ObjectInputStream類 和ObjectOutputStream類 向檔案中寫入3個User對象,追加1個User對象,最後再從檔案中讀回對象。
package cls;import java.io.*;import java.util.*;import cls.User;public class ObjectStreamDemo{ public static void main(String[] args) { User[] user = new User[]{new User("dogg",1),new User("catt",2),new User("pigg",3)}; // 向檔案中寫入對象 try { ObjectStreamDemo.writeObj(user,args[0]); } catch(Exception e) { System.out.println(e.toString()); } // 向檔案中追加對象 try { // 要追加的對象 User[] u = new User[]{new User("append1",4),new User("append2",5)}; ObjectStreamDemo.appendObj(u,args[0]); } catch(Exception e) { System.out.println(e.toString()); } // 讀取對象 try { List<User> list = ObjectStreamDemo.readObj(args[0]); // 輸出對象資訊 Iterator<User> it = list.iterator(); while(it.hasNext()) { User temp = it.next(); System.out.println(temp.getName() + "," + temp.getNum()); } } catch(Exception e) { System.out.println(e.toString()); } } static private void appendObj(Object[] objs,String fileName) throws Exception { File file = new File(fileName); // 以追加模式建立檔案流對象 FileOutputStream fis = new FileOutputStream(file,true); ObjectOutputStream oos = new ObjectOutputStream(fis) { // 重寫 writeStreamHeader()方法,空實現 protected void writeStreamHeader(){}; }; // 寫入資料 for(Object o : objs) { oos.writeObject(o); } // 關閉流 oos.close(); } static private List<User> readObj(String fileName) throws Exception { File file = new File(fileName); // 使用List儲存讀取出來的對象 ArrayList<User> list = new ArrayList<User>(); // 建立流對象 FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); // 讀取對象並放入List容器中 while(fis.available() > 0) { list.add((User)ois.readObject()); } ois.close(); return list; // 返回List } static private void writeObj(Object[] objs,String fileName) throws Exception { // 使用命令列參數中指定的檔案名稱 File file = new File(fileName); // 建立流對象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); // 寫入對象 for(Object o : objs) { oos.writeObject(o); } // 關閉流 oos.close(); }}
注意,當我們想要向一個已經存在的檔案中追加對象時,應該重寫ObjectOutputStream的writeStreamHeader()方法,並空實現。因為,ObjectOutputStream在寫入資料的時候會加上一個特別的流頭(Stream Header),在讀取資料的時候會先檢查這個流頭。所以我們在向檔案中追加對象的時候ObjectOutputStream就會再次向檔案中寫入流頭,這樣在讀取對象的時候會發生StreamCorrupedException異常。