原文地址:http://blog.csdn.net/yaolingrui/article/details/7633743
一、爲什麼要進行序列化
再介紹之前,我們有必要先了解下對象的生命週期,我們知道Java中的對象都是存在於堆內存中的,而堆內存是可以被垃圾回收器不定期回收的。從對象被創建到被回收這一段時間就是Java對象的生命週期,也即Java對象只存活於這個時間段內。
對象被垃圾回收器回收意味着對象和對象中的成員變量所佔的內存也就被回收,這意味着我們就再也得不到該對象的任何內容了,因爲已經被銷燬了嘛,當然我們可以再重新創建,但這時的對象的各種屬性都又被重新初始化了。所以如果我們需要保存某對象的狀態,然後再在未來的某段時間將該對象再恢復出來的話,則必須要在對象被銷燬即被垃圾回收器回收之前保存對象的狀態。要保存對象狀態的話,我們可以使用文件、數據庫,也可以使用序列化,這裏我們主要介紹對象序列化。我們很有必要了解這方面的內容,因爲對象序列化不僅在保存對象狀態時可以被用到(對象持久化),在Java中的遠程方法調用RMI也會被用到,在網絡中要傳輸對象的話,則必須要對對象進行序列化,關於RMI有機會我會再專門開貼介紹。
簡單總結起來,進行對象序列化的話的主要原因就是實現對象持久化和進行網絡傳輸,這裏先只介紹怎樣通過對象序列化保存對象的狀態。
下面我們通過一個簡單的例子來介紹下如何進行對象序列化。
二、怎樣進行對象序列化
假設我們要保存Person類的某三個對象的name、age、height這三個成員變量,當然這裏只是簡單舉例
我們先看下Person類,要序列化某個類的對象的話,則該類必要實現Serializable接口,從Java API中我們發現該接口是個空接口,即該接口中沒聲明任何方法。
- import java.io.Serializable;
- public class Person implements Serializable {
- int age;
- int height;
- String name;
- public Person(String name, int age, int height){
- this.name = name;
- this.age = age;
- this.height = height;
- }
- }
下面我們看一下如何來進行序列化,這其中主要涉及到Java的I/O方面的內容,主要用到兩個類FileOutputStream和ObjectOutputStream,FileOutputStream用於將字節輸出到文件,ObjectOutputStream通過調用writeObject方法將對象轉換爲可以寫出到流的數據。所以整個流程是這樣的:ObjectOutputStream將要序列化的對象轉換爲某種數據,然後通過FileOutputStream連接某磁盤文件,再對象轉化的數據轉化爲字節數據再將其寫出到磁盤文件。下面是具體代碼:
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectOutputStream;
- public class MyTestSer {
- /**
- * Java對象的序列化與反序列化
- */
- public static void main(String[] args) {
- Person zhangsan = new Person("zhangsan", 30, 170);
- Person lisi = new Person("lisi", 35, 175);
- Person wangwu = new Person("wangwu", 28, 178);
- try {
- //需要一個文件輸出流和對象輸出流;文件輸出流用於將字節輸出到文件,對象輸出流用於將對象輸出爲字節
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
- out.writeObject(zhangsan);
- out.writeObject(lisi);
- out.writeObject(wangwu);
- out.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
我們存儲的目的主要是爲了再恢復使用,下面我們來看下加上反序列化後的代碼:
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- public class MyTestSer {
- /**
- * Java對象的序列化與反序列化
- */
- public static void main(String[] args) {
- Person zhangsan = new Person("zhangsan", 30, 170);
- Person lisi = new Person("lisi", 35, 175);
- Person wangwu = new Person("wangwu", 28, 178);
- try {
- //需要一個文件輸出流和對象輸出流;文件輸出流用於將字節輸出到文件,對象輸出流用於將對象輸出爲字節
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
- out.writeObject(zhangsan);
- out.writeObject(lisi);
- out.writeObject(wangwu);
- } catch (IOException e) {
- e.printStackTrace();
- }
- try {
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
- Person one = (Person) in.readObject();
- Person two = (Person) in.readObject();
- Person three = (Person) in.readObject();
- System.out.println("name:"+one.name + " age:"+one.age + " height:"+one.height);
- System.out.println("name:"+two.name + " age:"+two.age + " height:"+two.height);
- System.out.println("name:"+three.name + " age:"+three.age + " height:"+three.height);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
輸出結果如下:
- name:zhangsan age:30 height:170
- name:zhangsan age:35 height:175
- name:zhangsan age:28 height:178
從添加的代碼我們可以看到進行反序列化也很簡單,主要用到的流是FileInputstream和ObjectInputstream正好與存儲時用到的流相對應。另外從結果順序我們可以看到反序列化後得到對象的順序與序列化時的順序一致。
四、總結
進行對象序列化主要目的是爲了保存對象的狀態(成員變量)。
進行序列化主要用到的流是FileOutputStream和ObjectOutputStream。FileOutputStream主要用於連接磁盤文件,並把字節寫出到該磁盤文件;ObjectOutputStream主要用於將對象寫出爲可轉化爲字節的數據。
要將某類的對象序列化,則該類必須實現Serializable接口,該接口僅是一個標誌,告訴JVM該類的對象可以被序列化。如果某類未實現Serializable接口,則該類對象不能實現序列化。
保存狀態的目的就是爲了在未來的某個時候再恢復保存的內容,這可以通過反序列化來實現。對象的反序列化過程與序列化正好相反,主要用到的兩個流是FileInputstream和ObjectInputStream。
反序列化後得到的對象的順序與保存時的順序一致。
五、補充
補充一:上面我們舉得例子很簡單,要保存的成員變量要麼是基本類型的要麼是String類型的。但有時成員變量有可能是引用類型的,這是的情況會複雜一點。那就是當要對某對象進行序列化時,該對象中的引用變量所引用的對象也會被同時序列化,並且該對象中如果也有引用變量的話則該對象也將被序列化。總結說來就是在序列化的時候,對象中的所有引用變量所對應的對象將會被同時序列化。這意味着,引用變量類型也都要實現Serializable接口。當然其他對象的序列化都是自動進行的。所以我們只要保證裏面的引用類型是都實現Serializable接口就行了,如果沒有的話,會在編譯時拋出異常。如果序列化的對象中包含沒有實現Serializable的成員變量的話,這時可以使用transient關鍵字,讓序列化的時候跳過該成員變量。使用關鍵字transient可以讓你在序列化的時候自動跳過transient所修飾的成員變量,在反序列化時這些變量會恢復到默認值。
補充二:如果某類實現了Serializable接口的話,其子類會自動編程可序列化的,這個好理解,繼承嘛。
補充三:在反序列化的時候,並不會調用對象的構造器,這也好理解,如果調用了構造器的話,對象的狀態不就又重新初始化了嗎。
補充四:我們說到對象序列化的是爲了保存對象的狀態,即對象的成員變量,所以靜態變量不會被序列化。