1. 程式人生 > >(二)遠端服務:Java 物件序列化和反序列化

(二)遠端服務:Java 物件序列化和反序列化

在遠端方法呼叫 RMI 學習的過程中,涉及到一個概念,序列化,本文進行詳述。

  • Java 物件的序列化和反序列化 的兩種應用場景

    有時候需要將 Java 物件儲存永久儲存,比如儲存到檔案中,過程:Java 物件 -> IO 物件流 -> 寫入檔案 -> 字串。當我們需要將文件中的字串恢復為 Java 物件的時候,需要相反的過程:字串 -> 讀檔案 -> IO 流 -> (強制)轉化為 Java 物件
    還有一種場景,在網路通訊中,物件要先變為IO流經過網路傳輸之後,再被還原為Java物件。過程:物件 -> 寫IO -> 讀IO -> 物件

  • 序列化是一個處理物件資訊的過程

    序列化 (Serialization)將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程。
    反序列化是將傳輸資訊恢復為物件的過程。

  • Java 物件被序列化的兩種方式

    方式一:Java 物件所屬的類需要直接或者間接實現 Serializable 介面。
    方式二:Java 物件所屬的類需要直接或者間接實現 Externalizable介面。這裡需要注意,如果通過繼承實現Externalizable介面,必須覆蓋重寫 writeExternal 和 readExternal 方法,否則子類新增屬性無法參與序列化。

  • Serializable 和 Externalizable 的區別

    Serializable 可以是間接或者直接實現,所在類的屬性都會被序列化。
    Serializable 的思路是,如果沒有特殊,本類以及子類的屬性將參與序列化。
    Externalizable 也可以是間接實現或者直接實現,但是其參與序列化和反序列化的屬相都需要通過重寫方法去指定。

  • transient
    實現 Serializable 的類 ,其屬性預設都會參與序列化和反序列化,如果不想某個屬性參與序列化,增加 transient 修飾即可。但是 實現 Externalizable 的類不會受transient 的影響。

    private transient String other;//transient 表示該欄位不參與序列化
  • Externalizable 需要覆蓋重寫的 writeExternal 和 readExternal 方法舉例


    //宣告屬性
    private String userName;
    private String password;
    private int age;
    private int age2;   

    //get set方法略

    //這兩個方法無需顯式呼叫,但是哪些屬性需要序列化,則需要單獨指明   
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(password);
        out.writeUTF(userName);
        out.writeInt(age);
        out.writeInt(age2);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        userName=in.readUTF();
        password=in.readUTF();
        age=in.readInt();
        age2=in.readInt();
    }
  • 序列號的生成

    Java 的序列化和反序列化工作是由 JVM 完成的,在序列化和反序列化的過程中,JVM 會根據類的路徑、名字、內容生成一個 long 型別的數字串(當然你也可以自己指定),這個數字串被稱為序列號。

    private static final long serialVersionUID = -5635658486326609381L;
  • 序列號的生成和 JVM 的版本有關係嗎?

    根據我的實驗,對於同一個路徑下,同一個類,在內容不變的情況下,JVM 生成的序列號是一致的。測試 jdk 版本有 jdk1.6、jdk1.7、jdk1.8。

  • 序列號是允許不顯式表述的

    序列號是可以不寫出來的,但是要求序列化參照的類和反序列化參照的類都不寫才可以。

  • 序列號的意義

    由於序列號的生成是和 類的路徑、名字、內容 有關,這意味著如果你的類的內容有修改,JVM 自動生成的序列號就會有變動,如果你在類中指定了序列號,JVM 就會以你指定的為準。這樣做是有好處的,可以提醒你遠端互動中兩端的類的版本不一致。但是這也有弊端,對於遠端呼叫來說,如果一方
    實體類有變動,那麼雙方生成的序列號可能就不一樣。所以序列號是為了保證序列化和反序列化雙方的一致性。

  • 一定要顯式的指定序列號

    由於我們無法確保類的內容不會更改,所以一定要寫序列號,這個是可以自定義的,比如寫成1L

  • Java 物件序列化會影響效能且需要平臺一致性

    顯然序列化會影響效能,而且需要同為 Java 平臺。

  • 序列化應用的簡單測試——以物件儲存到 txt 中為例

    • 實體類
    package common;
    import java.io.Serializable;
    public class Model implements Serializable{
    private static final long serialVersionUID = -3265803653258922833L;

    private String userName;
    private String password;
    private transient int age;
    private int age2;
    private transient String other;//transient 表示該欄位不參與序列化
    //get\set 方法 略

    //如果 implements Externalizable ,需在下面的方法中指明需序列化個反序列化欄位
    //序列化欄位
    /*@Override
    public void writeExternal(ObjectOutput out) throws IOException {

        out.writeUTF(password);
        out.writeUTF(userName);
        out.writeInt(age);
        out.writeInt(age2);
    }
    //反序列化欄位
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        userName=in.readUTF();
        password=in.readUTF();
        age=in.readInt();
        age2=in.readInt();
    }*/

}
  • 測試方法
    package common;

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

public class Test {

    //檔案路徑和名字
    private static String filePath="D://a.txt";


    public static void main(String[] args) {
        Model m=new Model();
        m.setUserName("jecket");
        m.setPassword("123456");
        m.setOther("other");

        //寫到檔案
        write(m);

        //從檔案讀取
        read();
    }

    //將物件儲存到 檔案
    public static void write(Model m){
         try {
            ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(filePath));
            oos.writeObject(m);
            oos.close();

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
    }


    //從物件中讀取 檔案
    public static void read(){
        try {
            @SuppressWarnings("resource")
            ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filePath));
            Model m=(Model)ois.readObject();
            System.out.println(m.getUserName());

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
    }

}