詳解java序列化(一)

轉自   http://blog.csdn.net/moreevan/article/details/6697777



我們可以通過序列化來保存一個對象的狀態(實例變量)到文件中,也可以從這個格式化的文件中很容易地讀取對象的狀態從而可以恢復我們保存的對象。

      用來實現序列化的類都在java.io包中,我們常用的類或接口有:ObjectOutputStream:提供序列化對象並把其寫入流的方法

ObjectInputStream:讀取流並反序列化對象

Serializable:一個對象想要被序列化,那麼它的類就要實現 此接口

      下面我們先通過一個簡單的例子演示一起序列化/反序列化的過程

Book.java

[java] view plaincopy
  1. package kevin.seria;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Book implements Serializable{  
  6.     private int isbn;  
  7.       
  8.     public Book(int isbn) {  
  9.         super();  
  10.         this.isbn = isbn;  
  11.     }  
  12.   
  13.     public int getIsbn() {  
  14.         return isbn;  
  15.     }  
  16.   
  17.     public void setIsbn(int isbn) {  
  18.         this.isbn = isbn;  
  19.     }  
  20.   
  21.     @Override  
  22.     public String toString() {  
  23.         return "Book [isbn=" + isbn + "]";  
  24.     }  
  25.       
  26.       
  27. }  

Student.java

[java] view plaincopy
  1. package kevin.seria;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Student implements Serializable {  
  6.     private Book book;  
  7.     private String name;  
  8.   
  9.     public Student(Book book, String name) {  
  10.         super();  
  11.         this.book = book;  
  12.         this.name = name;  
  13.     }  
  14.   
  15.     public Book getBook() {  
  16.         return book;  
  17.     }  
  18.   
  19.     public void setBook(Book book) {  
  20.         this.book = book;  
  21.     }  
  22.   
  23.     public String getName() {  
  24.         return name;  
  25.     }  
  26.   
  27.     public void setName(String name) {  
  28.         this.name = name;  
  29.     }  
  30.   
  31.     @Override  
  32.     public String toString() {  
  33.         return "Student [book=" + book + ", name=" + name + "]";  
  34.     }  
  35.   
  36. }  

Simulator.java

[java] view plaincopy
  1. package kevin.seria;  
  2.   
  3. import java.io.FileInputStream;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.io.ObjectInputStream;  
  8. import java.io.ObjectOutputStream;  
  9.   
  10. public class Simulator {  
  11.     public static void main(String[] args) {  
  12.         new Simulator().go();  
  13.     }  
  14.       
  15.     private void go(){  
  16.         Student student = new Student(new Book(2011),"kevin");  
  17.           
  18.         try {  
  19.             ObjectOutputStream out  = new ObjectOutputStream(new FileOutputStream("seria"));  
  20.             out.writeObject(student); //  
  21.             System.out.println("object has been written..");  
  22.             out.close();  
  23.         } catch (FileNotFoundException e) {  
  24.             e.printStackTrace();  
  25.         } catch (IOException e) {  
  26.             e.printStackTrace();  
  27.         }  
  28.           
  29.         try{  
  30.             ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));  
  31.             Student studentRead = (Student) in.readObject();  
  32.             System.out.println("object read here:");  
  33.             System.out.println(studentRead);  
  34.         }catch(FileNotFoundException e){  
  35.             e.printStackTrace();  
  36.         } catch (IOException e) {  
  37.             e.printStackTrace();  
  38.         } catch (ClassNotFoundException e) {  
  39.             // TODO Auto-generated catch block  
  40.             e.printStackTrace();  
  41.         }  
  42.     }  
  43. }  

這個程序運行的結果如下:

 


我們可以看到,讀取到的對象與保存的對象狀態一樣。這裏有幾點需要說明一下:

1、        基本類型 的數據可以直接序列化

2、        對象要被序列化,它的類必須要實現Serializable接口;如果一個類中有引用類型的實例變量,這個引用類型也要實現Serializable接口。比如上面 的例子中,Student類中有一個Book類型 的實例就是,要想讓Student的對象成功序列化,那麼Book也必須要實現Serializable接口。

3、        我們看這個語句:

ObjectOutputStreamout  = newObjectOutputStream(new FileOutputStream("seria"));

 

我們知道 FileOutputStream類有一個帶有兩個參數的重載Constructor——FileOutputStream(String,boolean),其第二個參數如果爲true且String代表的文件存在,那麼將把新的內容寫到原來文件的末尾而非重寫這個文件,這裏我們不能用這個版本的構造函數,也就是說我們必須重寫這個文件,否則在讀取這個文件反序列化的過程中就會拋出異常,導致只有我們第一次寫到這個文件中的對象可以被反序列化,之後程序就會出錯。

 

下面的問題是如果 我們上面 用到的Book類沒有實現Serializable接口,但是我們還想序列化Student類的對象 ,怎麼辦。

Java爲我們提供了transient這個關鍵字。如果一個變量被聲明成transient,那麼 在序列化的過程 中,這個變量是會被無視的。我們還是通過對上面的代碼進行小的修改來說明 這個問題。

新的Book類不實現Serializable接口

[java] view plaincopy
  1. package kevin.seria;  
  2.   
  3. public class Book{  
  4.     private int isbn;  
  5.       
  6.     public Book(int isbn) {  
  7.         super();  
  8.         this.isbn = isbn;  
  9.     }  
  10.   
  11.     public int getIsbn() {  
  12.         return isbn;  
  13.     }  
  14.   
  15.     public void setIsbn(int isbn) {  
  16.         this.isbn = isbn;  
  17.     }  
  18.   
  19.     @Override  
  20.     public String toString() {  
  21.         return "Book [isbn=" + isbn + "]";  
  22.     }  
  23.       
  24.       
  25. }  

因爲我們要序列化Student類的對象,所以我們必須實現Serializable接口,然而Book是Student的一個實例變量,它的類沒有實現Serializable接口,所以爲了順序完成序列化,我們把這個實例變量聲明爲transient以在序列化的過程中跳過它。

[java] view plaincopy
  1. package kevin.seria;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Student implements Serializable {  
  6.     private transient Book book;  
  7.     private String name;  
  8.   
  9.     public Student(Book book, String name) {  
  10.         super();  
  11.         this.book = book;  
  12.         this.name = name;  
  13.     }  
  14.   
  15.     public Book getBook() {  
  16.         return book;  
  17.     }  
  18.   
  19.     public void setBook(Book book) {  
  20.         this.book = book;  
  21.     }  
  22.   
  23.     public String getName() {  
  24.         return name;  
  25.     }  
  26.   
  27.     public void setName(String name) {  
  28.         this.name = name;  
  29.     }  
  30.   
  31.     @Override  
  32.     public String toString() {  
  33.         return "Student [book=" + book + ", name=" + name + "]";  
  34.     }  
  35.   
  36. }  

Simulator.java和上面的一樣,我們看一下運行結果:


可以看到,student對象被成功的序列化了。因爲序列化過程中跳過了Book實例,所以當恢復對象狀態的時候 ,它被賦予了默認值null,這也就意味着我們不能使用它。那如果Book類沒有實現Serializable接口,但我們還想對它的狀態進行保存,這可以實現 嗎?答案是肯定的,到底如何肯定,請聽小弟我慢慢道來。。。

Java針對這種情況有一種特殊的機制—— 一組私有(回調)方法(這個我們馬上在代碼中看到),可以在要被序列化的類中實現它們,在序列化和的序列化的過程中它們會被自動調用。所以在這組方法中我們可以調用ObjectOutputStream/ObjectInputStream的一些有用方法來實現對象狀態的保存。下面還是通過例子來說明:

Book類和Simulator類都不變,我們來看 一下新的Student類:

[java] view plaincopy
  1. package kevin.seria;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.ObjectInputStream;  
  5. import java.io.ObjectOutputStream;  
  6. import java.io.Serializable;  
  7.   
  8. public class Student implements Serializable {  
  9.     private transient Book book;  
  10.     private String name;  
  11.   
  12.     public Student(Book book, String name) {  
  13.         super();  
  14.         this.book = book;  
  15.         this.name = name;  
  16.     }  
  17.   
  18.     public Book getBook() {  
  19.         return book;  
  20.     }  
  21.   
  22.     public void setBook(Book book) {  
  23.         this.book = book;  
  24.     }  
  25.   
  26.     public String getName() {  
  27.         return name;  
  28.     }  
  29.   
  30.     public void setName(String name) {  
  31.         this.name = name;  
  32.     }  
  33.       
  34.     //下面這兩個方法就是那組特殊的私有方法,它們會在序列化、反序列化的過程 中被自動調用   
  35.     //我們必須保證方法的簽名和下面的兩個方法完全相同  
  36.       
  37.     //這個方法會在序列化的過程中被調用   
  38.     private void writeObject(ObjectOutputStream out){  
  39.         try {  
  40.             out.defaultWriteObject(); //這個方法會把這當前中非靜態變量和非transient變量寫到流中  
  41.                                       //在這裏我們就把name寫到了流中。  
  42.             //因爲我們要保存Book的狀態,所以我們還要想辦法把其狀態寫入流中  
  43.             out.writeInt(book.getIsbn());//ObjectOutputStream中提供了寫基本類型數據的方法  
  44.             //out.close();//注意,這句千萬不能有,否剛將直接導致寫入失敗  
  45.         } catch (IOException e) {  
  46.             e.printStackTrace();  
  47.         }   
  48.     }  
  49.       
  50.     //這個方法會在反序列化的過程中被調用  
  51.     private void readObject(ObjectInputStream in){  
  52.         try {  
  53.             in.defaultReadObject(); //和defaultWriteObject()方法相對應,默認的反序列化方法,會從流中讀取  
  54.                                     //非靜態變量和非transient變量  
  55.             int isbn  = in.readInt(); //用這個方法來讀取一個int型值,這裏我們是讀取書號  
  56.             book  = new Book(isbn); //這裏我們就通過讀取的 保存的狀態構造 了一個和原來一樣的Book對象  
  57.             //in.close();同樣的這句也不能有  
  58.         } catch (IOException e) {  
  59.             e.printStackTrace();  
  60.         } catch (ClassNotFoundException e) {  
  61.             e.printStackTrace();  
  62.         }  
  63.     }  
  64.       
  65.     @Override  
  66.     public String toString() {  
  67.         return "Student [book=" + book + ", name=" + name + "]";  
  68.     }  
  69.   
  70. }  

好,看下程序運行的結果,我們這次期望的是Book的狀態得到了保存,ok ,come on guys, check it out



OH YES!正如預料 的一樣,我們成功了。呵呵 。要注意的點我在代碼的註釋中有說明,請好好看下代碼。

還有一點我在代碼 中沒寫出來 ,就是一定要注意寫入和讀取的順序一定要對應。像上面如果你是先寫基本類型數據的話,那在讀取的時候也一定要先讀取基本類型的數據,這個原因我想大家都清楚,文件是有position的。

最後,還有兩個問題:

1、        如果一個類沒有實現Serializable接口,但是它的基類實現 了,這個類可不可以序列化?

2、        和上面相反,如果一個類實現了Serializable接口,但是它的父類沒有實現 ,這個類可不可以序列化?

 

第1個問題:一個類實現 了某接口,那麼它的所有子類都間接實現了此接口,所以它可以被 序列化。

第2個問題:Object是每個類的超類,但是它沒有實現 Serializable接口,但是我們照樣在序列化對象,所以說明一個類要序列化,它的父類不一定要實現Serializable接口。但是在父類中定義 的狀態能被正確 的保存以及讀取嗎?這個我將在下一篇文章中用一個例子來說明(馬上就更新),請列位看官多多關注 ,呵呵。

 

還有一點,序列化保存對象的狀態,而靜態(static)變量不是對象的 狀態,所以它們不會被序列化。

8/18/2011 By Kevin(MoreeVan)

(END)

關於第2個問題的解答我已經更新到此:詳解java序列化(二)http://blog.csdn.net/moreevan/article/details/6698529


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章