<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
"); //-->

博客專(zhuān)欄

EEPW首頁(yè) > 博客 > 面試官:三年工作經(jīng)驗,你連序列化都說(shuō)不明白?

面試官:三年工作經(jīng)驗,你連序列化都說(shuō)不明白?

發(fā)布人:編碼之外 時(shí)間:2021-05-05 來(lái)源:工程師 發(fā)布文章

什么是序列化、反序列化

  • 序列化:把Java對象轉換為字節序列的過(guò)程。

  • 反序列化:把字節序列恢復為Java對象的過(guò)程。

序列化的作用

  • 1、可以把對象的字節序列永久地保存到硬盤(pán)上,通常存放在一個(gè)文件中;(持久化對象)

  • 2、也可以在網(wǎng)絡(luò )上傳輸對象的字節序列;(網(wǎng)絡(luò )傳輸對象)

序列化在Java中的用法

在Java中序列化的實(shí)現:將需要被序列化的類(lèi)實(shí)現Serializable接口,該接口沒(méi)有需要實(shí)現的方法,實(shí)現該接口只是為了標注該對象是可被序列化的,然后使用一個(gè)輸出流(如:FileOutputStream)來(lái)構造一個(gè)ObjectOutputStream(對象輸出流)對象,接著(zhù),使用ObjectOutputStream對象的writeObject(Object obj)方法就可以將參數為obj的對象寫(xiě)出(即保存其狀態(tài)),要恢復的話(huà)則用ObjectInputStream(對象輸入流)。

如下為序列化、反序列化簡(jiǎn)單案例 Test01

 java.io.FileInputStream;
 java.io.FileOutputStream;
 java.io.IOException;
 java.io.ObjectInputStream;
 java.io.ObjectOutputStream;
 java.io.Serializable;

 {
    {
        
        serializable();
        
        deserialization();
    }

    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream())) {
            Person person = Person();
            person.setName();
            person.setAge();
            oos.writeObject(person);
        } (IOException e) {
            e.printStackTrace();
        }
    }

    {
         (ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            Person person = (Person) ois.readObject();
            System.out.println(person);
        } (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}


{
        serialVersionUID = -;
     String name;
      age;

    {
         name;
    }

    {
        .name = name;
    }

    {
         age;
    }

    {
        .age = age;
    }

    
    {
          +
                 + name + +
                 + age +
                ;
    }
}

上面案例中只是簡(jiǎn)單的進(jìn)行了對象序列化和反序列化,但是序列化和反序列化過(guò)程中有很多值得思考的細節問(wèn)題,例如:

1、序列化版本號(serialVersionUID)問(wèn)題
2、靜態(tài)變量序列化
3、父類(lèi)的序列化與 transient 關(guān)鍵字
4、自定義序列化規則
5、序列化存儲規則

1、序列化版本號(serialVersionUID)問(wèn)題

在寫(xiě)Java程序中有時(shí)我們經(jīng)常會(huì )看到類(lèi)中會(huì )有一個(gè)序列化版本號:serialVersionUID。這個(gè)值有的類(lèi)是1L或者是自動(dòng)生成的。

private static final long serialVersionUID = 1L;

或者

private static final long serialVersionUID = -2052381772192998351L;

當在反序列化時(shí)JVM需要判斷需要轉化的兩個(gè)類(lèi)是不是同一個(gè)類(lèi),于是就需要一個(gè)序列化版本號。如果在反序列化的時(shí)候兩個(gè)類(lèi)的serialVersionUID不一樣則JVM會(huì )拋出java.io.InvalidClassException的異常;如果serialVersionUID一致則表明可以轉換。

如果可序列化類(lèi)未顯式聲明 serialVersionUID,則序列化運行時(shí)將基于該類(lèi)的各個(gè)方面計算該類(lèi)的默認 serialVersionUID 值。不過(guò),強烈建議 所有可序列化類(lèi)都顯式聲明 serialVersionUID 值,原因是計算默認的 serialVersionUID 對類(lèi)的詳細信息具有較高的敏感性,根據編譯器實(shí)現的不同可能千差萬(wàn)別,這樣在反序列化過(guò)程中可能會(huì )導致意外的 InvalidClassException,所以這種方式不支持反序列化重構。所謂重構就是可以對類(lèi)增加或者減少屬性字段,也就是說(shuō)即使兩個(gè)類(lèi)并不完全一致,他們也是可以轉換的,只不過(guò)如果找不到對應的字段,它的值會(huì )被設為默認值。

因此,為保證 serialVersionUID 值跨不同 java 編譯器實(shí)現的一致性或代碼重構時(shí),序列化類(lèi)必須聲明一個(gè)明確的 serialVersionUID 值。還強烈建議使用 private 修飾符顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應用于直接聲明類(lèi) -- serialVersionUID 字段作為繼承成員沒(méi)有用處。數組類(lèi)不能聲明一個(gè)明確的 serialVersionUID,因此它們總是具有默認的計算值,但是數組類(lèi)沒(méi)有匹配 serialVersionUID 值的要求。

還有一個(gè)常見(jiàn)的值是1L(或者其他固定值),如果所有類(lèi)都這么寫(xiě)那還怎么區分它們,這個(gè)字段還有什么意義嗎?有的!首先如果兩個(gè)類(lèi)有了相同的反序列化版本號,比如1L,那么表明這兩個(gè)類(lèi)是支持在反序列化時(shí)重構的。但是會(huì )有一個(gè)明顯的問(wèn)題:如果兩個(gè)類(lèi)是完全不同的,但是他們的序列化版本號都是1L,那么對于JVM來(lái)說(shuō)他們也是可以進(jìn)行反序列化重構的!這這顯然是不對的,但是回過(guò)頭來(lái)說(shuō)這種明顯的,愚蠢的錯誤在實(shí)際開(kāi)發(fā)中是不太可能會(huì )犯的,如果不是那么嚴謹的話(huà)用1L是個(gè)不錯的選擇。

一般的情況下這個(gè)值是顯式地指定為一個(gè)64位的哈希字段,比如你寫(xiě)了一個(gè)類(lèi)實(shí)現了java.io.Serializable接口,在idea里會(huì )提示你加上這個(gè)序列化id。這樣做可以區分不同的類(lèi),也支持反序列化重構。

總結如下:

serialVersionUID區分不同類(lèi)支持相同類(lèi)的重構




不指定YEsNO
1LNOYES
64位哈希值YESYES

簡(jiǎn)單而言,從嚴謹性的角度來(lái)說(shuō),指定64位哈希值>默認值1L>不指定serialVersionUID值,具體怎么使用就看你的需求了。

2、靜態(tài)變量序列化

 java.io.FileInputStream;
 java.io.FileOutputStream;
 java.io.IOException;
 java.io.ObjectInputStream;
 java.io.ObjectOutputStream;
 java.io.Serializable;

 {
    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream());
             ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            
            Person person = Person();
            person.setName();
            person.setAge();
            oos.writeObject(person);

            
            Person.avgAge =;

            Person person1 = (Person) ois.readObject();
            
            System.out.println(person1.avgAge);
        } (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}


{
        serialVersionUID = -;
     String name;
      age;
       avgAge =;

    {
         name;
    }

    {
        .name = name;
    }

    {
         age;
    }

    {
        .age = age;
    }

    
    {
          +
                 + name + +
                 + age +
                ;
    }
}

執行結果顯示如下:

圖片

我們看到 Test02.java將對象序列化后,修改靜態(tài)變量的數值再將序列化對象讀取出來(lái),然后通過(guò)讀取出來(lái)的對象獲得靜態(tài)變量的數值并打印出來(lái),最后的輸出是 10,之所以打印 10 的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對象的狀態(tài),靜態(tài)變量屬于類(lèi)的狀態(tài),因此 序列化并不保存靜態(tài)變量 。

3、父類(lèi)的序列化與 transient關(guān)鍵字

情境 :一個(gè)子類(lèi)實(shí)現了 Serializable 接口,它的父類(lèi)都沒(méi)有實(shí)現 Serializable 接口,序列化該子類(lèi)對象,然后反序列化后輸出父類(lèi)定義的某變量的數值,該變量數值與序列化時(shí)的數值不同。

解決 :要想將父類(lèi)對象也序列化,就需要讓父類(lèi)也實(shí)現 Serializable 接口 。如果父類(lèi)不實(shí)現的話(huà)的,就需要有默認的無(wú)參的構造函數 。在父類(lèi)沒(méi)有實(shí)現 Serializable 接口時(shí),虛擬機是不會(huì )序列化父對象的,而一個(gè) Java 對象的構造必須先有父對象,才有子對象,反序列化也不例外。所以反序列化時(shí),為了構造父對象,只能調用父類(lèi)的無(wú)參構造函數作為默認的父對象。因此當我們取父對象的變量值時(shí),它的值是調用父類(lèi)無(wú)參構造函數后的值。如果你考慮到這種序列化的情況,在父類(lèi)無(wú)參構造函數中對變量進(jìn)行初始化,否則的話(huà),父類(lèi)變量值都是默認聲明的值,如 int 型的默認是 0,string 型的默認是 null。

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

3-1、特性使用案例:

我們熟悉使用 transient 關(guān)鍵字可以使得字段不被序列化,那么還有別的方法嗎?根據父類(lèi)對象序列化的規則,我們可以將不需要被序列化的字段抽取出來(lái)放到父類(lèi)中,子類(lèi)實(shí)現 Serializable 接口,父類(lèi)不實(shí)現,根據父類(lèi)序列化規則,父類(lèi)的字段數據將不被序列化,形成類(lèi)圖如下圖所示。


上圖中可以看出,attr1、attr2、attr3、attr5 都不會(huì )被序列化,放在父類(lèi)中的好處在于當有另外一個(gè) Child 類(lèi)時(shí),attr1、attr2、attr3 依然不會(huì )被序列化,不用重復書(shū)寫(xiě) transient 關(guān)鍵字,代碼簡(jiǎn)潔。

4、自定義序列化規則

在序列化和反序列化過(guò)程中需要特殊處理的類(lèi)必須使用下列準確簽名來(lái)實(shí)現特殊方法:

;
;
;

writeObject 方法負責寫(xiě)入特定類(lèi)的對象的狀態(tài),以便相應的 readObject 方法可以恢復它。通過(guò)調用 oos.defaultWriteObject 可以調用保存 Object 的字段的默認機制。該方法本身不需要涉及屬于其超類(lèi)或子類(lèi)的狀態(tài)。通過(guò)使用 writeObject 方法或使用 DataOutput 支持的用于基本數據類(lèi)型的方法將各個(gè)字段寫(xiě)入 ObjectOutputStream,狀態(tài)可以被保存。

readObject 方法負責從流中讀取并恢復類(lèi)字段。它可以調用 oin.defaultReadObject 來(lái)調用默認機制,以恢復對象的非靜態(tài)和非瞬態(tài)(非 transient 修飾)字段。defaultReadObject方法使用流來(lái)分配保存在流中的對象的字段當前對象中相應命名的字段。這用于處理類(lèi)演化后需要添加新字段的情形。該方法本身不需要涉及屬于其超類(lèi)或子類(lèi)的狀態(tài)。通過(guò)使用 writeObject 方法或使用 DataOutput 支持的用于基本數據類(lèi)型的方法將各個(gè)字段寫(xiě)入 ObjectOutputStream,狀態(tài)可以被保存。

在序列化流不列出給定類(lèi)作為將被反序列化對象的超類(lèi)的情況下,readObjectNoData 方法負責初始化特定類(lèi)的對象狀態(tài)。這在接收方使用的反序列化實(shí)例類(lèi)的版本不同于發(fā)送方,并且接收者版本擴展的類(lèi)不是發(fā)送者版本擴展的類(lèi)時(shí)發(fā)生。在序列化流已經(jīng)被篡改時(shí)也將發(fā)生;因此,不管源流是“敵意的”還是不完整的,readObjectNoData 方法都可以用來(lái)正確地初始化反序列化的對象。

readObjectNoData()應用示例:

 java.io.FileOutputStream;
 java.io.ObjectOutputStream;
 java.io.Serializable;


 {
    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream())) {
            Person person = Person();
            person.setAge();
            oos.writeObject(person);
        } (Exception e) {
            e.printStackTrace();
        }
    }
}

{
      age;

    {
        .age = age;
    }

    {
         .age;
    }
}



 java.io.FileInputStream;
 java.io.ObjectInputStream;
 java.io.Serializable;


 {
    {
         (ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            Person person = (Person) ois.readObject();
            System.out.println(person.getName());
        } (Exception e) {
            e.printStackTrace();
        }
    }
}



{
      age;

    {
        .age = age;
    }

    {
         .age;
    }
}

{
     String name;

    {
        .name = name;
    }

    {
         .name;
    }

    {
        .name =;
    }
}

將對象寫(xiě)入流時(shí)需要指定要使用的替代對象的可序列化類(lèi),應使用準確的簽名來(lái)實(shí)現此特殊方法:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

此 writeReplace 方法將由序列化調用,前提是如果此方法存在,而且它可以通過(guò)被序列化對象的類(lèi)中定義的一個(gè)方法訪(fǎng)問(wèn)。因此,該方法可以擁有私有 (private)、受保護的 (protected) 和包私有 (package-private) 訪(fǎng)問(wèn)。子類(lèi)對此方法的訪(fǎng)問(wèn)遵循 java 訪(fǎng)問(wèn)規則。

在從流中讀取類(lèi)的一個(gè)實(shí)例時(shí)需要指定替代的類(lèi)應使用的準確簽名來(lái)實(shí)現此特殊方法。

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

此 readResolve 方法遵循與 writeReplace 相同的調用規則和訪(fǎng)問(wèn)規則。

TIP: readResolve常用來(lái)反序列單例類(lèi),保證單例類(lèi)的唯一性

例如:

 java.io.FileInputStream;
 java.io.FileOutputStream;
 java.io.IOException;
 java.io.ObjectInputStream;
 java.io.ObjectOutputStream;
 java.io.Serializable;

 {
    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream())) {
            oos.writeObject(Brand.NIKE);
        }
         (ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            Brand b = (Brand) ois.readObject();
            
            System.out.println(b == Brand.NIKE);
        }
    }
}

{
      val;

    {
        .val = val;
    }

    
       Brand NIKE = Brand();
       Brand ADDIDAS = Brand();
}

答案很顯然是false,因為Brand.NIKE是程序中創(chuàng )建的對象,而b是從磁盤(pán)中讀取并恢復過(guò)來(lái)的對象,兩者明顯來(lái)源不同,因此必然內存空間是不同的,引用(地址)顯然也是不同的;

但這不是我們想看到的,因為我們把Brand設計成枚舉類(lèi)型,不管是程序中創(chuàng )建的還是從哪里讀取的,其必須應該和枚舉常量完全相等,這才是枚舉的意義??!


而此時(shí)readResolve就派上用場(chǎng)了,我們可以這樣實(shí)現readResolve:

 java.io.FileInputStream;
 java.io.FileOutputStream;
 java.io.IOException;
 java.io.ObjectInputStream;
 java.io.ObjectOutputStream;
 java.io.ObjectStreamException;
 java.io.Serializable;

 {
    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream())) {
            oos.writeObject(Brand.NIKE);
        }
         (ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            Brand b = (Brand) ois.readObject();
            
            System.out.println(b == Brand.NIKE);
        }
    }
}

{
      val;

    {
        .val = val;
    }

    
       Brand NIKE = Brand();
       Brand ADDIDAS = Brand();

    {
         (val ==) {
             NIKE;
        }
         (val ==) {
             ADDIDAS;
        }
         ;
    }
}

改造以后,不管來(lái)源如何,最終得到的都將是程序中Brand的枚舉值了!因為readResolve的代碼在執行時(shí)已經(jīng)進(jìn)入了程序內存環(huán)境,因此其返回的NIKE和ADDIDAS都將是Brand的靜態(tài)成員對象;

因此保護性恢復的含義就在此:首先恢復的時(shí)候沒(méi)有改變其值(val的值沒(méi)有改變)同時(shí)恢復的時(shí)候又能正常實(shí)現枚舉值的對比(地址也完全相同);

4-1、對敏感字段加密

情境:服務(wù)器端給客戶(hù)端發(fā)送序列化對象數據,對象中有一些數據是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時(shí),進(jìn)行加密,而客戶(hù)端如果擁有解密的密鑰,只有在客戶(hù)端進(jìn)行反序列化時(shí),才可以對密碼進(jìn)行讀取,這樣可以一定程度保證序列化對象的數據安全。

解決:在序列化過(guò)程中,虛擬機會(huì )試圖調用對象類(lèi)里的 writeObject 和 readObject 方法,進(jìn)行用戶(hù)自定義的序列化和反序列化,該方法必須要被聲明為private,如果沒(méi)有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶(hù)自定義的 writeObject 和 readObject 方法可以允許用戶(hù)控制序列化的過(guò)程,比如可以在序列化的過(guò)程中動(dòng)態(tài)改變序列化的數值?;谶@個(gè)原理,可以在實(shí)際應用中得到使用,用于敏感字段的加密工作,如下代碼展示了這個(gè)過(guò)程。

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

   {
    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream());
             ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            oos.writeObject( Account());

            Account account = (Account) ois.readObject();
            System..println( + account.getPassword());
        } (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

    {
     String password =;

    {
         password;
    }

    {
        .password = password;
    }

    {
         {
            ObjectOutputStream.PutField putFields =.putFields();
            System..println( + password);
            
            password =;
            putFields.put(, password);
            System..println( + password);
            .writeFields();
        } (IOException e) {
            e.printStackTrace();
        }
    }

    {
         {
            ObjectInputStream.GetField readFields =.readFields();
            Object = readFields.(,);
            System..println( +.toString());
            
            password =;
        } (IOException e) {
            e.printStackTrace();
        } (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

上述代碼中的 writeObject 方法中,對密碼進(jìn)行了加密,在 readObject 中則對 password 進(jìn)行解密,只有擁有密鑰的客戶(hù)端,才可以正確的解析出密碼,確保了數據的安全。

4-2、序列化SDK中不可序列化的類(lèi)型

4-1、對敏感字段加密案例使用 writeObject 和 readObject 進(jìn)行了對象屬性值加解密操作,有時(shí)我們想將對象中的某一字段序列化,但它在SDK中的定義卻是不可序列化的類(lèi)型,這樣的話(huà)我們也必須把他標注為 transient 才能保證正常序列化,可是不能序列化又怎么恢復呢?這就用到了上面提到的 writeObject 和 readObject 方法,進(jìn)行自定義序列化操作了。

示例:java.awt.geom包中的Point2D.Double類(lèi)就是不可序列化的,因為該類(lèi)沒(méi)有實(shí)現Serializable接口

 java.awt.geom.Point2D;
 java.io.FileInputStream;
 java.io.FileOutputStream;
 java.io.IOException;
 java.io.ObjectInputStream;
 java.io.ObjectOutputStream;
 java.io.Serializable;

 {
    {
        LabeledPoint label = LabeledPoint(,,);
         {
            
            System.out.println(label);
            ObjectOutputStream out = ObjectOutputStream( FileOutputStream());
            
            out.writeObject(label);
            out.close();
            
            System.out.println(label);
            ObjectInputStream in = ObjectInputStream( FileInputStream());
            LabeledPoint label1 = (LabeledPoint) in.readObject();
            in.close();
            
            System.out.println(label1);
        } (Exception e) {
            e.printStackTrace();
        }
    }
}

{
     String label;
    
      Point2D.Double point;

    {
        label = str;
        
        point = Point2D.Double(x, y);
    }

    
    {
        oos.defaultWriteObject();
        oos.writeDouble(point.getX());
        oos.writeDouble(point.getY());
    }

    {
        ois.defaultReadObject();
         x = ois.readDouble() +;
         y = ois.readDouble() +;
        point = Point2D.Double(x, y);
    }

    
    {
          +
                 + label + +
                 + point +
                ;
    }
}


4-1、序列化SDK中不可序列化的類(lèi)型案例中,你會(huì )發(fā)現調用了defaultWriteObject()和defaultReadObject()。它們做的是默認的序列化進(jìn)程,就像寫(xiě)/讀所有的non-transient和 non-static字段(但他們不會(huì )去做serialVersionUID的檢查)。通常說(shuō)來(lái),所有我們想要自己處理的字段都應該聲明為transient。這樣的話(huà) defaultWriteObject/defaultReadObject 便可以專(zhuān)注于其余字段,而我們則可為這些特定的字段(指transient)定制序列化。使用那兩個(gè)默認的方法并不是強制的,而是給予了處理復雜應用時(shí)更多的靈活性。

5、序列化存儲規則

5-1、存儲兩次相同對象

 java.io.File;
 java.io.FileInputStream;
 java.io.FileOutputStream;
 java.io.IOException;
 java.io.ObjectInputStream;
 java.io.ObjectOutputStream;
 java.io.Serializable;

 {
    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream());
             ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            
            Account account = Account();
            account.setPassword();
            oos.writeObject(account);
            oos.flush();
            System.out.println( File().length());
            oos.writeObject(account);
            System.out.println( File().length());

            
            Account account1 = (Account) ois.readObject();
            Account account2 = (Account) ois.readObject();

            
            System.out.println(account1 == account2);
        } (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

{
     String password;

    {
         password;
    }

    {
        .password = password;
    }
}

上述代碼中對同一對象兩次寫(xiě)入文件,打印出寫(xiě)入一次對象后的存儲大小和寫(xiě)入兩次后的存儲大小,然后從文件中反序列化出兩個(gè)對象,比較這兩個(gè)對象是否為同一對象。一般的思維是,兩次寫(xiě)入對象,文件大小會(huì )變?yōu)閮杀兜拇笮?,反序列化時(shí),由于從文件讀取,生成了兩個(gè)對象,判斷相等時(shí)應該是輸入 false 才對,但

我們看到,第二次寫(xiě)入對象時(shí)文件只增加了 5 字節,并且兩個(gè)對象是相等的,因為Java 序列化機制為了節省磁盤(pán)空間,具有特定的存儲規則,當寫(xiě)入文件的為同一對象時(shí),并不會(huì )再將對象的內容進(jìn)行存儲,而只是再次存儲一份引用,上面增加的 5 字節的存儲空間就是新增引用和一些控制信息的空間。反序列化時(shí),恢復引用關(guān)系,使得上述代碼中的 account1 和 account2 指向唯一的對象,二者相等,輸出 true。該存儲規則極大的節省了存儲空間

5-2、存儲兩次相同對象,更改屬性值

 java.io.FileInputStream;
 java.io.FileOutputStream;
 java.io.IOException;
 java.io.ObjectInputStream;
 java.io.ObjectOutputStream;
 java.io.Serializable;

 {
    {
         (ObjectOutputStream oos = ObjectOutputStream( FileOutputStream());
             ObjectInputStream ois = ObjectInputStream( FileInputStream())) {
            Account account = Account();
            account.setPassword();
            oos.writeObject(account);
            oos.flush();
            account.setPassword();
            oos.writeObject(account);

            
            Account account1 = (Account) ois.readObject();
            Account account2 = (Account) ois.readObject();

            System.out.println(account1.getPassword());
            System.out.println(account2.getPassword());
        } (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

{
     String password;

    {
         password;
    }

    {
        .password = password;
    }
}

上述代碼的目的是希望將 account 對象兩次保存到 object.txt 文件中,寫(xiě)入一次以后修改對象屬性值再次保存第二次,然后從 object.txt 中再依次讀出兩個(gè)對象,輸出這兩個(gè)對象的 password 屬性值。上述代碼的目的原本是希望一次性傳輸對象修改前后的狀態(tài)。

結果兩個(gè)輸出的都是 123456, 原因就是第一次寫(xiě)入對象以后,第二次再試圖寫(xiě)的時(shí)候,虛擬機根據引用關(guān)系知道已經(jīng)有一個(gè)相同對象已經(jīng)寫(xiě)入文件,因此只保存第二次寫(xiě)的引用,所以讀取時(shí),都是第一次保存的對象。這也驗證了 5-1、存儲兩次相同對象案例的現象,相同對象存在只會(huì )存儲引用,不再進(jìn)行對象存儲,所以第二次修改的屬性未變化。讀者在使用一個(gè)文件多次 writeObject 需要特別注意這個(gè)問(wèn)題。


*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。



關(guān)鍵詞: 序列化

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>