Java中的序列化與反序列化
序列化定義
將對象轉換為字節流保存起來,並在以后還原這個對象,這種機制叫做對象序列化。
將一個對象保存到永久存儲設備上稱為持久化。
一個對象要想能夠實現序列化,必須實現java.io.Serializable接口。該接口中沒有定義任何方法,是一個標識性接口(Marker Interface),當一個類實現了該接口,就表示這個類的對象是可以序列化的。
序列化(serialization)是把一個對象的狀態寫入一個字節流的過程。當你想要把你的程序狀態存到一個固定的存儲區域,例如文件時,它是很管用的。
從程序到外面叫序列化,從外面讀回來叫反序列化。
序列化特點
假設一個被序列化的對象引用了其他對象,同樣,其他對象又引用了更多的對象。這一系列的對象和它們的關系形成了一個順序圖表。
對象序列化和反序列化工具被設計出來並在這一假定條件下運行良好。
如果你試圖序列化一個對象圖表中頂層的對象,所有其他的引用對象都被循環地定位和序列化;同樣,在反序列化過程中,所有的這些對象以及它們的引用都被正確地恢復。
序列化實現細節
只有實現Serializable接口的對象可以被序列化工具存儲和恢復。
Serializable接口沒有定義任何成員,它只用來表示一個類可以被序列化。
如果一個類可以被序列化,它的所有子類都可以序列化。
當一個對象被序列化時,只保存對象的非靜態成員變量,不能保存任何的成員方法和靜態的成員變量。
如果一個對象的成員變量是一個對象,那么這個對象的數據成員也會被保存。
如果一個可序列化的對象包含對某個不可序列化對象的引用,那么整個序列化操作將會失敗,並且會拋出一個NotSerializableException。
我們可以將這個不可序列化的引用標記為transient,那么原對象仍然可以序列化。使用transient修飾的變量將不會被序列化。
反序列化時不會調用對象的任何構造方法,僅僅根據所保存的對象狀態信息,在內存中重新構建對象。
ObjectOutput接口
ObjectOutput接口繼承DataOutput接口並且支持對象序列化。
特別注意writeObject(Object obj)方法,它被稱為序列化一個對象。
所有這些方法在出錯情況下引發IOException異常。
ObjectOutputStream類
對象的輸出流。ObjectOutputStream類繼承OutputStream類和實現ObjectOutput接口。它負責向流寫入對象。
該類的構造函數ObjectOutputStream(OutputStream out),參數是序列化對象將要寫入的輸出流。
根據它的構造函數可以判斷它是一個包裝類,是一個過濾流。
ObjectInput接口
ObjectInput接口繼承DataInput接口。它支持對象序列化。
特別注意readObject()方法,它叫反序列化一個對象。
所有這些方法在出錯情況下引發IOException異常。
ObjectInputStream類
ObjectInputStream類繼承InputStream類,實現ObjectInput接口。它負責從流中讀取對象。
該類的構造函數ObjectInputStream(InputStream in),參數是序列化對象將被讀取的輸入流。
ObjectInputStream類是一個過濾流,它包裝了節點流。
序列化的程序例子SerializableTest1:這個例子定義了一個類叫Person,程序實現了Person類對象的序列化(寫出)和反序列化(讀入)。

import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerializableTest1 { public static void main(String[] args) throws Exception { Person p1 = new Person("ZhangSan", 29, 1.78); Person p2 = new Person("LiSi", 50, 1.7); Person p3 = new Person("WangWu", 46, 1.67); FileOutputStream fos = new FileOutputStream("Person.txt");// 當前項目路徑下的一個文本文檔 ObjectOutputStream oos = new ObjectOutputStream(fos); // 用序列化的方式將對象存儲在文件中 oos.writeObject(p1); oos.writeObject(p2); oos.writeObject(p3); oos.close(); // 反序列化,將對象信息讀出 FileInputStream fis = new FileInputStream("Person.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Person person = null; for (int i = 0; i < 3; ++i) { person = (Person) ois.readObject(); System.out.println(person.name + ", " + person.age + ", " + person.height); } ois.close(); } } class Person implements Serializable { // 這個類需要實現Serializable接口 // 否則在序列化時會拋出java.io.NotSerializableException transient String name; transient int age; double height; // 加上transient關鍵字后將不被序列化 public Person(String name, int age, double height) { this.name = name; this.age = age; this.height = height; } }
在程序中嘗試可知,如果加上transient關鍵字,變量將不被序列化,反序列化時讀取不到,輸出默認值,如整形和double型變量輸出為默認值0,字符串將輸出為null。
一個序列化中的特殊情況
在序列化和反序列化中需要特殊處理的Serializable類應該實現以下方法:
private void writeObject (java.io.ObjectOutputStream stream) throws IOException
private void readObject (java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
這兩個方法不屬於任何一個類或任何一個接口,是非常特殊的方法。
只要我們提供了這兩個方法,在序列化反序列化的時候它們會得到自動調用。
如根據第一個程序改編的程序2,注意只是將Person類改名為Person2,然后加入上面兩個特殊的函數。注意這時候去掉了所有的transient關鍵字。

import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerializableTest2 { public static void main(String[] args) throws Exception { Person2 p1 = new Person2("ZhangSan", 29, 1.78); Person2 p2 = new Person2("LiSi", 50, 1.7); Person2 p3 = new Person2("WangWu", 46, 1.67); FileOutputStream fos = new FileOutputStream("Person2.txt");// 當前項目路徑下的一個文本文檔 ObjectOutputStream oos = new ObjectOutputStream(fos); // 用序列化的方式將對象存儲在文件中 oos.writeObject(p1); oos.writeObject(p2); oos.writeObject(p3); oos.close(); // 反序列化,將對象信息讀出 FileInputStream fis = new FileInputStream("Person2.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Person2 person = null; for (int i = 0; i < 3; ++i) { person = (Person2) ois.readObject(); System.out.println(person.name + ", " + person.age + ", " + person.height); } ois.close(); } } class Person2 implements Serializable { // 這個類需要實現Serializable接口 // 否則在序列化時會拋出java.io.NotSerializableException String name; int age; double height; // 加上transient關鍵字后將不被序列化 public Person2(String name, int age, double height) { this.name = name; this.age = age; this.height = height; } private void writeObject(java.io.ObjectOutputStream out) throws IOException { System.out.println("writeObject Method"); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { System.out.println("readObject Method"); } }
程序輸出如下:
writeObject Method
writeObject Method
writeObject Method
readObject Method
null, 0, 0.0
readObject Method
null, 0, 0.0
readObject Method
null, 0, 0.0
可以看到這兩個方法是自動被調用的,但要注意的是反序列化后得到的值全是默認值。
雖然所有的變量都不是transient關鍵字修飾,但是程序在序列化時只處理了變量名字,並沒有將變量值序列化。
當提供了這兩個方法后,序列化和反序列化就完全由我們自己控制。
如果我們在方法中不寫入相關代碼,則反序列化后並不能讀入什么值,所有變量都是默認值。加入相關變量的序列化和反序列化代碼后才能真的執行操作。
我們在一個待序列化/反序列化的類中實現了以上兩個private方法(方法聲明要與上面的完全保持一致,具體在Serializable接口的文檔中也有解釋),那么就允許我們以更加底層、更加細粒度的方式控制序列化/反序列化的過程。
如下代碼,在前面代碼的基礎上加入了其中兩個變量的序列化和反序列化代碼。

import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerializableTest2 { public static void main(String[] args) throws Exception { Person2 p1 = new Person2("ZhangSan", 29, 1.78); Person2 p2 = new Person2("LiSi", 50, 1.7); Person2 p3 = new Person2("WangWu", 46, 1.67); FileOutputStream fos = new FileOutputStream("Person2.txt");// 當前項目路徑下的一個文本文檔 ObjectOutputStream oos = new ObjectOutputStream(fos); // 用序列化的方式將對象存儲在文件中 oos.writeObject(p1); oos.writeObject(p2); oos.writeObject(p3); oos.close(); // 反序列化,將對象信息讀出 FileInputStream fis = new FileInputStream("Person2.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Person2 person = null; for (int i = 0; i < 3; ++i) { person = (Person2) ois.readObject(); System.out.println(person.name + ", " + person.age + ", " + person.height); } ois.close(); } } class Person2 implements Serializable { // 這個類需要實現Serializable接口 // 否則在序列化時會拋出java.io.NotSerializableException String name; int age; double height; // 加上transient關鍵字后將不被序列化 public Person2(String name, int age, double height) { this.name = name; this.age = age; this.height = height; } private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeUTF(name); out.writeInt(age); System.out.println("writeObject Method"); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { name = in.readUTF(); age = in.readInt(); System.out.println("readObject Method"); } }
參考資料
聖思園張龍老師Java SE系列視頻。