Java中實現序列化的兩種方式 Serializable 接口和 Externalizable接口

對象的序列化就是將對象寫入輸出流中。html

反序列化就是從輸入流中將對象讀取出來。java

用來實現序列化的類都在java.io包中,咱們經常使用的類或接口有:api

ObjectOutputStream:提供序列化對象並把其寫入流的方法數組

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

Serializable:一個對象想要被序列化,那麼它的類就要實現 此接口,這個對象的全部屬性(包括private屬性、包括其引用的對象)均可以被序列化和反序列化來保存、傳遞。函數

Externalizable:他是Serializable接口的子類,有時咱們不但願序列化那麼多,可使用這個接口,這個接口的writeExternal()和readExternal()方法能夠指定序列化哪些屬性;測試

        可是若是你只想隱藏一個屬性,好比用戶對象user的密碼pwd,若是使用Externalizable,併除了pwd以外的每一個屬性都寫在writeExternal()方法裏,這樣顯得麻煩,可使用Serializable接口,並在要隱藏的屬性pwd前面加上transient就能夠實現了。this

 

 

方法一:spa

實現Serializable接口。.net

序列化的時候的一個關鍵字:transient(臨時的)。它聲明的變量實行序列化操做的時候不會寫入到序列化文件中去。

 

例子:

package demo2;

import java.io.Serializable;

//實現Serializable接口才能被序列化
public class UserInfo implements Serializable{
    private String userName;
    private String usePass;
    private transient int userAge;//使用transient關鍵字修飾的變量不會被序列化
    public String getUserName() {
        return userName;
    }
    public UserInfo() {
        userAge=20;
    }
    public UserInfo(String userName, String usePass, int userAge) {
        super();
        this.userName = userName;
        this.usePass = usePass;
        this.userAge = userAge;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUsePass() {
        return usePass;
    }
    public void setUsePass(String usePass) {
        this.usePass = usePass;
    }
    public int getUserAge() {
        return userAge;
    }
    public void setUserAge(int userAge) {
        this.userAge = userAge;
    }
    @Override 
    public String toString() {
        return "UserInfo [userName=" + userName + ", usePass=" + usePass + ",userAge="+(userAge==0?"NOT SET":userAge)+"]";
    }
    

}
package demo2;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

public class UserInfoTest {
    
    /**
     * 序列化對象到文件
     * @param fileName
     */
    public static void serialize(String fileName){
        try {
            ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));
            
            out.writeObject("序列化的日期是:");//序列化一個字符串到文件
            out.writeObject(new Date());//序列化一個當前日期對象到文件
            UserInfo userInfo=new UserInfo("郭大俠","961012",21);
            out.writeObject(userInfo);//序列化一個會員對象
            
            out.close();
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 從文件中反序列化對象
     * @param fileName
     */
    public static void deserialize(String fileName){
        try {
            ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));
            
            String str=(String) in.readObject();//剛纔的字符串對象
            Date date=(Date) in.readObject();//日期對象
            UserInfo userInfo=(UserInfo) in.readObject();//會員對象
            
            System.out.println(str);
            System.out.println(date);
            System.out.println(userInfo);
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args){
//        serialize("text");
        deserialize("text");//這裏userAge取讀不到是由於使用了transient修飾,因此獲得的是默認值
        
        /**
         * 我修改了一下UserInfo的無參構造,在無參構造中給userAge屬性賦值蛋反序列化獲得的結果仍是同樣。
         * 得出結論:
         * 當從磁盤中讀出某個類的實例時,實際上並不會執行這個類的構造函數,   
         * 而是載入了一個該類對象的持久化狀態,並將這個狀態賦值給該類的另外一個對象。  
         */
    }

}

 

 

方法二:

實現Externalizable接口:

使用這個接口的場合是這樣的:

一個類中咱們只但願序列化一部分數據,其餘數據都使用transient修飾的話顯得有點麻煩,這時候咱們使用externalizable接口,指定序列化的屬性。

例子:

 
 

package demo2;

 
 

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

 
 

//實現Externalizable接口序列化
public class UserInfo implements Externalizable{
 private String userName;
 private String usePass;
 private int userAge;
 public String getUserName() {
  return userName;
 }
 public UserInfo() {
  userAge=20;//這個是在第二次測試使用,判斷反序列化是否經過構造器
 }
 public UserInfo(String userName, String usePass, int userAge) {
  super();
  this.userName = userName;
  this.usePass = usePass;
  this.userAge = userAge;
 }
 public void setUserName(String userName) {
  this.userName = userName;
 }
 public String getUsePass() {
  return usePass;
 }
 public void setUsePass(String usePass) {
  this.usePass = usePass;
 }
 public int getUserAge() {
  return userAge;
 }
 public void setUserAge(int userAge) {
  this.userAge = userAge;
 }
 @Override
 public String toString() {
  return "UserInfo [userName=" + userName + ", usePass=" + usePass + ",userAge="+(userAge==0?"NOT SET":userAge)+"]";
 }
 public void writeExternal(ObjectOutput out) throws IOException {
  /*
   * 指定序列化時候寫入的屬性。這裏仍然不寫入年齡
   */
  out.writeObject(userName);
  out.writeObject(usePass);
  
 }
 public void readExternal(ObjectInput in) throws IOException,
   ClassNotFoundException {
  /*
   * 指定反序列化的時候讀取屬性的順序以及讀取的屬性

  * 若是你寫反了屬性讀取的順序,你能夠發現反序列化的讀取的對象的指定的屬性值也會與你寫的讀取方式一一對應。由於在文件中裝載對象是有序的
   */
  userName=(String) in.readObject();
  usePass=(String) in.readObject();
 }
 

 
 

}

 

測試:

 
 

package demo2;

 
 

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

 
 

public class UserInfoTest {
 
 /**
  * 序列化對象到文件
  * @param fileName
  */
 public static void serialize(String fileName){
  try {
   ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));
   
   out.writeObject("序列化的日期是:");//序列化一個字符串到文件
   out.writeObject(new Date());//序列化一個當前日期對象到文件
   UserInfo userInfo=new UserInfo("郭大俠","961012",21);
   out.writeObject(userInfo);//序列化一個會員對象
   
   out.close();
   
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
 
 /**
  * 從文件中反序列化對象
  * @param fileName
  */
 public static void deserialize(String fileName){
  try {
   ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));
   
   String str=(String) in.readObject();//剛纔的字符串對象
   Date date=(Date) in.readObject();//日期對象
   UserInfo userInfo=(UserInfo) in.readObject();//會員對象
   
   System.out.println(str);
   System.out.println(date);
   System.out.println(userInfo);
   
  } catch (FileNotFoundException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
 }
 
 public static void main(String[] args){
//  serialize("text");
  deserialize("text");
  
  /**
   * 我修改了一下UserInfo的無參構造,在無參構造中給userAge屬性賦值蛋反序列化獲得的結果是userAge變成了20。
   * 得出結論:
   * 當從磁盤中讀出某個類的實例時,若是該實例使用的是Externalizable序列化,會執行這個類的構造函數,
   * 而後調用readExternal給其餘屬性賦值 
   */
 }

 
 

}

 

 原理分析:

總結:
首先,咱們在序列化UserInfo對象的時候,因爲這個類實現了Externalizable 接口,在writeExternal()方法裏定義了哪些屬性能夠序列化,哪些不能夠序列化,因此,對象在通過這裏就把規定能被序列化的序列化保存文件,不能序列化的不處理,而後在反序列的時候自動調用readExternal()方法,根據序列順序挨個讀取進行反序列,並自動封裝成對象返回,而後在測試類接收,就完成了反序列

 

一些api:

Externalizable 實例類的惟一特性是能夠被寫入序列化流中,該類負責保存和恢復實例內容。 若某個要徹底控制某一對象及其超類型的流格式和內容,則它要實現 Externalizable 接口的 writeExternal 和 readExternal 方法。這些方法必須顯式與超類型進行協調以保存其狀態。這些方法將代替定製的 writeObject 和 readObject 方法實現。

writeExternal(ObjectOutput out)
          該對象可實現 writeExternal 方法來保存其內容,它能夠經過調用 DataOutput 的方法來保存其基本值,或調用 ObjectOutput 的 writeObject 方法來保存對象、字符串和數組。

readExternal(ObjectInput in)
          對象實現 readExternal 方法來恢復其內容,它經過調用 DataInput 的方法來恢復其基礎類型,調用 readObject 來恢復對象、字符串和數組。

 

externalizable和Serializable的區別:(靜態屬性持保留意見,60%偏向不能直接序列化)

1:

實現serializable接口是默認序列化全部屬性,若是有不須要序列化的屬性使用transient修飾。

externalizable接口是serializable的子類,實現這個接口須要重寫writeExternal和readExternal方法,指定對象序列化的屬性和從序列化文件中讀取對象屬性的行爲。

2:

實現serializable接口的對象序列化文件進行反序列化不走構造方法,載入的是該類對象的一個持久化狀態,再將這個狀態賦值給該類的另外一個變量

實現externalizable接口的對象序列化文件進行反序列化先走構造方法獲得控對象,而後調用readExternal方法讀取序列化文件中的內容給對應的屬性賦值。

 

serialVersionUID做用:序列化時爲了保持版本的兼容性,即在版本升級時反序列化仍保持對象的惟一性。 有兩種生成方式: 一個是默認的1L,好比:private static final long se...

serialVersionUID做用: 
序列化時爲了保持版本的兼容性,即在版本升級時反序列化仍保持對象的惟一性。 
有兩種生成方式: 
一個是默認的1L,好比:private static final long serialVersionUID = 1L;
一個是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段;

 

幾個問題:

一、        若是一個類沒有實現Serializable接口,可是它的基類實現 了,這個類可不能夠序列化?

二、        和上面相反,若是一個類實現了Serializable接口,可是它的父類沒有實現 ,這個類可不能夠序列化?

 

第1個問題:一個類實現 了某接口,那麼它的全部子類都間接實現了此接口,因此它能夠被 序列化。

第2個問題:Object是每一個類的超類,可是它沒有實現 Serializable接口,可是咱們照樣在序列化對象,因此說明一個類要序列化,它的父類不必定要實現Serializable接口。可是在父類中定義 的狀態能被正確 的保存以及讀取嗎?

第3個問題:若是將一個對象寫入某文件(好比是a),那麼以後對這個對象進行一些修改,而後把修改的對象再寫入文件a,那麼文件a中會包含該對象的兩個 版本嗎?

 

關於幾個問題的答案見這個博客:http://blog.csdn.net/moreevan/article/details/6698529(我是從上面copy轉過來的,不肯深刻了)

相關文章
相關標籤/搜索