Java中實現序列化

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

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

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

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

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

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

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

Externalizable:他是Serializable接口的子類,有時我們不希望序列化那麼多,可以使用這個接口,這個接口的writeExternal()和readExternal()方法可以指定序列化哪些屬性;

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

 

 

方法一:

實現Serializable接口。

序列化的時候的一個關鍵字: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位的哈希字段;

 

幾個問題:

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

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

 

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

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

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

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