Java序列化和反序列化

什么是序列化?


序列化是將對(duì)象存儲(chǔ)為二進(jìn)制格式。
在序列化的過程中,對(duì)象和它的元數(shù)據(jù)(比如對(duì)象的類名和它的屬性名稱)存儲(chǔ)為二進(jìn)制格式。并且在需要的時(shí)候可以恢復(fù)對(duì)象(反序列化)。

對(duì)象序列化的兩個(gè)使用場景:

  • 對(duì)象持久化:將對(duì)象的狀態(tài)持久化,比如存儲(chǔ)到數(shù)據(jù)庫中。
  • 對(duì)象遠(yuǎn)程傳輸:將對(duì)象從一臺(tái)計(jì)算機(jī)發(fā)送到另外一臺(tái)計(jì)算機(jī)。

實(shí)現(xiàn)序列化


如果希望對(duì)象能夠序列化需要實(shí)現(xiàn)一個(gè)接口,即序列化接口(Java.io.Serializable )。

import java.io.Serializable;

public class Person implements Serializable {

}

通過調(diào)用ObjectOutputStream.writeObject(Object o) 來實(shí)現(xiàn)將對(duì)象持久化
之后通過ObjectInputStream.readObject() 實(shí)現(xiàn)反序列化。


父類序列化與Transient關(guān)鍵字


情景:一個(gè)子類實(shí)現(xiàn)了Serializable接口,它的父類沒有實(shí)現(xiàn)Serializable接口序列化該對(duì)子類對(duì)象,然后反序列化輸出父類某域的數(shù)值,該變量與序列化時(shí)的數(shù)值不同(為0或?yàn)閚ull)。

解決:要想父類對(duì)象也序列化,就需要讓父類也實(shí)現(xiàn)Serializable接口。如果父類不實(shí)現(xiàn)的話就需要有默認(rèn)的無參構(gòu)造函數(shù),在父類沒有實(shí)現(xiàn)Serializable接口時(shí),虛擬機(jī)是不會(huì)序列化父對(duì)象的,而一個(gè)Java對(duì)象的實(shí)例化過程必先伴隨著父類對(duì)象的實(shí)例化,實(shí)例化也不例外。只能調(diào)用父類對(duì)象的無參構(gòu)造函數(shù)作為父類實(shí)例化。

Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對(duì)象型的是 null。

我們熟悉使用 Transient 關(guān)鍵字可以使得字段不被序列化,那么還有別的方法嗎?根據(jù)父類對(duì)象序列化的規(guī)則,我們可以將不需要被序列化的字段抽取出來放到父類中,子類實(shí)現(xiàn) Serializable 接口,父類不實(shí)現(xiàn),根據(jù)父類序列化規(guī)則,父類的字段數(shù)據(jù)將不被序列化。

通過以上我們大致可以分析得出序列化和反序列化的實(shí)現(xiàn)是區(qū)別與構(gòu)造函數(shù)的,在反序列化的過程中并不會(huì)去顯式調(diào)用已經(jīng)序列化對(duì)象的構(gòu)造函數(shù),而是當(dāng)其父類沒有實(shí)現(xiàn)序列化接口時(shí)候再去調(diào)用無參構(gòu)造函數(shù),通過這樣的機(jī)制可以包裝那些不需要序列化的域,所有語言的設(shè)計(jì)都是有其深層含義的 。這和Java語言設(shè)計(jì)的品味有關(guān)。

當(dāng)然當(dāng)一個(gè)父類對(duì)象實(shí)現(xiàn)序列化之后其子類默認(rèn)實(shí)現(xiàn)序列化。


序列化的存儲(chǔ)規(guī)則



ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));
Test test = new Test();
test.i = 1;
out.writeObject(test);
out.flush();
test.i = 2;
out.writeObject(test);
out.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
Test t1 = (Test) oin.readObject();
Test t2 = (Test) oin.readObject();
System.out.println(t1.i);
System.out.println(t2.i);


按照我們目前的理解輸出結(jié)果應(yīng)該分別為1和2,但事實(shí)上輸出的結(jié)果都是1,Java 序列化機(jī)制為了節(jié)省磁盤空間,具有特定的存儲(chǔ)規(guī)則,當(dāng)寫入文件的為同一對(duì)象時(shí),并不會(huì)再將對(duì)象的內(nèi)容進(jìn)行存儲(chǔ),而只是再次存儲(chǔ)一份引用,而當(dāng)反序列化時(shí),恢復(fù)引用關(guān)系,使得代碼中的 t1 和 t2 指向唯一的對(duì)象,第一次寫入對(duì)象以后,第二次再試圖寫的時(shí)候,虛擬機(jī)根據(jù)引用關(guān)系知道已經(jīng)有一個(gè)相同對(duì)象已經(jīng)寫入文件,因此只保存第二次寫的引用,所以讀取時(shí),都是第一次保存的對(duì)象。因此寫入相同對(duì)象時(shí)候需要注意這個(gè)問題。


序列化處理敏感字段


在序列化過程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值。基于這個(gè)原理,可以在實(shí)際應(yīng)用中得到使用,用于敏感字段的加密工作。

public class Person
    implements java.io.Serializable
{
    public Person(String fn, String ln, int a)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
    }
 
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
    public Person getSpouse() { return spouse; }
     
    public void setFirstName(String value) { firstName = value; }
    public void setLastName(String value) { lastName = value; }
    public void setAge(int value) { age = value; }
    public void setSpouse(Person value) { spouse = value; }
 
    private void writeObject(java.io.ObjectOutputStream stream)
        throws java.io.IOException
    {
        //簡單加密數(shù)據(jù)
        age = age << 2;
        stream.defaultWriteObject();
    }
 
    private void readObject(java.io.ObjectInputStream stream)
        throws java.io.IOException, ClassNotFoundException
    {
        stream.defaultReadObject();
 
        // 數(shù)據(jù)的解碼
        age = age << 2;
    }
     
    public String toString()
    {
        return "[Person: firstName=" + firstName + 
            " lastName=" + lastName +
            " age=" + age +
            " spouse=" + (spouse!=null ? spouse.getFirstName() : "[null]") +
            "]";
    }      
 
    private String firstName;
    private String lastName;
    private int age;
    private Person spouse;
}

序列化 ID 問題

虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致。即使雖然兩個(gè)類的功能代碼完全一致,但是序列化 ID 不同,他們無法相互序列化和反序列化。序列化 ID 在 Eclipse 下提供了兩種生成策略,一個(gè)是固定的 1L,一個(gè)是隨機(jī)生成一個(gè)不重復(fù)的 long 類型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成),在這里有一個(gè)建議,如果沒有特殊需求,就是用默認(rèn)的 1L 就可以,這樣可以確保代碼一致時(shí)反序列化成功。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候,通過改變序列化 ID 可以用來限制某些用戶的使用。

private static final long serialVersionUID = 1L;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 11,163評(píng)論 0 24
  • 1.背景 某天,我在寫代碼定義 bean 的時(shí)候,順手寫了個(gè) public class User implemen...
    李眼鏡閱讀 854評(píng)論 0 2
  • 1. Java序列化和反序列化(What) Java序列化(Serialize)是指將一個(gè)Java對(duì)象寫入IO流中...
    悠揚(yáng)前奏閱讀 971評(píng)論 2 1
  • 使用Java的序列化和反序列化可以實(shí)現(xiàn)信息的持久存儲(chǔ)要實(shí)現(xiàn)序列化必須實(shí)現(xiàn)java.io.Serializable這...
    那臉憔悴閱讀 754評(píng)論 0 52
  • Java 提供了一種對(duì)象序列化的機(jī)制,該機(jī)制中,一個(gè)對(duì)象可以被表示為一個(gè)字節(jié)序列,該字節(jié)序列包括該對(duì)象的數(shù)據(jù)、有關(guān)...
    笑漫人生閱讀 472評(píng)論 0 0

友情鏈接更多精彩內(nèi)容