成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

JAVA 序列化

GeekQiaQia / 649人閱讀

摘要:靜態(tài)變量序列化情境查看清單的代碼。之所以打印的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類(lèi)的狀態(tài),因此序列化并不保存靜態(tài)變量。解決要想將父類(lèi)對(duì)象也序列化,就需要讓父類(lèi)也實(shí)現(xiàn)接口。

原文 https://www.ibm.com/developer...

 引言

將 Java 對(duì)象序列化為二進(jìn)制文件的 Java 序列化技術(shù)是 Java 系列技術(shù)中一個(gè)較為重要的技術(shù)點(diǎn),在大部分情況下,開(kāi)發(fā)人員只需要了解被序列化的類(lèi)需要實(shí)現(xiàn) Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream 進(jìn)行對(duì)象的讀寫(xiě)。然而在有些情況下,光知道這些還遠(yuǎn)遠(yuǎn)不夠,文章列舉了筆者遇到的一些真實(shí)情境,它們與 Java 序列化相關(guān),通過(guò)分析情境出現(xiàn)的原因,使讀者輕松牢記 Java 序列化中的一些高級(jí)認(rèn)識(shí)。

序列化 ID 問(wèn)題

情境:兩個(gè)客戶(hù)端 A 和 B 試圖通過(guò)網(wǎng)絡(luò)傳遞對(duì)象數(shù)據(jù),A 端將對(duì)象 C 序列化為二進(jìn)制數(shù)據(jù)再傳給 B,B 反序列化得到 C。
問(wèn)題:C 對(duì)象的全類(lèi)路徑假設(shè)為 com.inout.Test,在 A 和 B 端都有這么一個(gè)類(lèi)文件,功能代碼完全一致。也都實(shí)現(xiàn)了 Serializable 接口,但是反序列化時(shí)總是提示不成功。
解決:虛擬機(jī)是否允許反序列化,不僅取決于類(lèi)路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類(lèi)的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清單 1 中,雖然兩個(gè)類(lèi)的功能代碼完全一致,但是序列化 ID 不同,他們無(wú)法相互序列化和反序列化。

清單 1. 相同功能代碼不同序列化 ID 的類(lèi)對(duì)比

package com.inout; 
import java.io.Serializable;
public class A implements Serializable { 
    private static final long serialVersionUID = 1L; 
    private String name; 
    
    public String getName() 
    { 
        return name; 
    }
    public void setName(String name) 
    { 
        this.name = name; 
    } 
} 
 
package com.inout; 
import java.io.Serializable; 
public class A implements Serializable { 
    private static final long serialVersionUID = 2L; 
    private String name; 
    
    public String getName() 
    { 
        return name; 
    } 
    public void setName(String name) 
    { 
        this.name = name; 
    } 
}

序列化 ID 在 Eclipse 下提供了兩種生成策略,一個(gè)是固定的 1L,一個(gè)是隨機(jī)生成一個(gè)不重復(fù)的 long 類(lèi)型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成),在這里有一個(gè)建議,如果沒(méi)有特殊需求,就是用默認(rèn)的 1L 就可以,這樣可以確保代碼一致時(shí)反序列化成功。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候,通過(guò)改變序列化 ID 可以用來(lái)限制某些用戶(hù)的使用。

特性使用案例Fa?ade

讀者應(yīng)該聽(tīng)過(guò) Fa?ade 模式,它是為應(yīng)用程序提供統(tǒng)一的訪問(wèn)接口,案例程序中的 Client 客戶(hù)端使用了該模式,案例程序結(jié)構(gòu)圖如圖 1 所示。

Client 端通過(guò) Fa?ade Object 才可以與業(yè)務(wù)邏輯對(duì)象進(jìn)行交互。而客戶(hù)端的 Fa?ade Object 不能直接由 Client 生成,而是需要 Server 端生成,然后序列化后通過(guò)網(wǎng)絡(luò)將二進(jìn)制對(duì)象數(shù)據(jù)傳給 Client,Client 負(fù)責(zé)反序列化得到 Fa?ade 對(duì)象。該模式可以使得 Client 端程序的使用需要服務(wù)器端的許可,同時(shí) Client 端和服務(wù)器端的 Fa?ade Object 類(lèi)需要保持一致。當(dāng)服務(wù)器端想要進(jìn)行版本更新時(shí),只要將服務(wù)器端的 Fa?ade Object 類(lèi)的序列化 ID 再次生成,當(dāng) Client 端反序列化 Fa?ade Object 就會(huì)失敗,也就是強(qiáng)制 Client 端從服務(wù)器端獲取最新程序。

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

情境:查看清單 2 的代碼。
清單 2. 靜態(tài)變量序列化問(wèn)題代碼

public class Test implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    public static int staticVar = 5;
 
    public static void main(String[] args) {
        try {
            //初始時(shí)staticVar為5
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            out.writeObject(new Test());
            out.close();
 
            //序列化后修改為10
            Test.staticVar = 10;
 
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                    "result.obj"));
            Test t = (Test) oin.readObject();
            oin.close();
             
            //再讀取,通過(guò)t.staticVar打印新的值
            System.out.println(t.staticVar);
             
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

清單 2 中的 main 方法,將對(duì)象序列化后,修改靜態(tài)變量的數(shù)值,再將序列化對(duì)象讀取出來(lái),然后通過(guò)讀取出來(lái)的對(duì)象獲得靜態(tài)變量的數(shù)值并打印出來(lái)。依照清單 2,這個(gè) System.out.println(t.staticVar) 語(yǔ)句輸出的是 10 還是 5 呢?

最后的輸出是 10,對(duì)于無(wú)法理解的讀者認(rèn)為,打印的 staticVar 是從讀取的對(duì)象里獲得的,應(yīng)該是保存時(shí)的狀態(tài)才對(duì)。之所以打印 10 的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類(lèi)的狀態(tài),因此 序列化并不保存靜態(tài)變量。

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

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

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

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

特性使用案例
我們熟悉使用 Transient 關(guān)鍵字可以使得字段不被序列化,那么還有別的方法嗎?根據(jù)父類(lèi)對(duì)象序列化的規(guī)則,我們可以將不需要被序列化的字段抽取出來(lái)放到父類(lèi)中,子類(lèi)實(shí)現(xiàn) Serializable 接口,父類(lèi)不實(shí)現(xiàn),根據(jù)父類(lèi)序列化規(guī)則,父類(lèi)的字段數(shù)據(jù)將不被序列化,形成類(lèi)圖如圖 2 所示。
圖 2. 案例程序類(lèi)圖
圖 2. 案例程序類(lèi)圖
上圖中可以看出,attr1、attr2、attr3、attr5 都不會(huì)被序列化,放在父類(lèi)中的好處在于當(dāng)有另外一個(gè) Child 類(lèi)時(shí),attr1、attr2、attr3 依然不會(huì)被序列化,不用重復(fù)抒寫(xiě) transient,代碼簡(jiǎn)潔。

對(duì)敏感字段加密

情境:服務(wù)器端給客戶(hù)端發(fā)送序列化對(duì)象數(shù)據(jù),對(duì)象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對(duì)該密碼字段在序列化時(shí),進(jìn)行加密,而客戶(hù)端如果擁有解密的密鑰,只有在客戶(hù)端進(jìn)行反序列化時(shí),才可以對(duì)密碼進(jìn)行讀取,這樣可以一定程度保證序列化對(duì)象的數(shù)據(jù)安全。
解決:在序列化過(guò)程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類(lèi)里的 writeObject 和 readObject 方法,進(jìn)行用戶(hù)自定義的序列化和反序列化,如果沒(méi)有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶(hù)自定義的 writeObject 和 readObject 方法可以允許用戶(hù)控制序列化的過(guò)程,比如可以在序列化的過(guò)程中動(dòng)態(tài)改變序列化的數(shù)值?;谶@個(gè)原理,可以在實(shí)際應(yīng)用中得到使用,用于敏感字段的加密工作,清單 3 展示了這個(gè)過(guò)程。
清單 3. 靜態(tài)變量序列化問(wèn)題代碼

private static final long serialVersionUID = 1L;
 
   private String password = "pass";
 
   public String getPassword() {
       return password;
   }
 
   public void setPassword(String password) {
       this.password = password;
   }
 
   private void writeObject(ObjectOutputStream out) {
       try {
           PutField putFields = out.putFields();
           System.out.println("原密碼:" + password);
           password = "encryption";//模擬加密
           putFields.put("password", password);
           System.out.println("加密后的密碼" + password);
           out.writeFields();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
 
   private void readObject(ObjectInputStream in) {
       try {
           GetField readFields = in.readFields();
           Object object = readFields.get("password", "");
           System.out.println("要解密的字符串:" + object.toString());
           password = "pass";//模擬解密,需要獲得本地的密鑰
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
 
   }
 
   public static void main(String[] args) {
       try {
           ObjectOutputStream out = new ObjectOutputStream(
                   new FileOutputStream("result.obj"));
           out.writeObject(new Test());
           out.close();
 
           ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                   "result.obj"));
           Test t = (Test) oin.readObject();
           System.out.println("解密后的字符串:" + t.getPassword());
           oin.close();
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
   }

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

特性使用案例:密碼加密

RMI 技術(shù)是完全基于 Java 序列化技術(shù)的,服務(wù)器端接口調(diào)用所需要的參數(shù)對(duì)象來(lái)至于客戶(hù)端,它們通過(guò)網(wǎng)絡(luò)相互傳輸。這就涉及 RMI 的安全傳輸?shù)膯?wèn)題。一些敏感的字段,如用戶(hù)名密碼(用戶(hù)登錄時(shí)需要對(duì)密碼進(jìn)行傳輸),我們希望對(duì)其進(jìn)行加密,這時(shí),就可以采用本節(jié)介紹的方法在客戶(hù)端對(duì)密碼進(jìn)行加密,服務(wù)器端進(jìn)行解密,確保數(shù)據(jù)傳輸?shù)陌踩浴?/p> 序列化存儲(chǔ)規(guī)則

情境:?jiǎn)栴}代碼如清單 4 所示。
清單 4. 存儲(chǔ)規(guī)則問(wèn)題代碼

ObjectOutputStream out = new ObjectOutputStream(
                   new FileOutputStream("result.obj"));
   Test test = new Test();
   //試圖將對(duì)象兩次寫(xiě)入文件
   out.writeObject(test);
   out.flush();
   System.out.println(new File("result.obj").length());
   out.writeObject(test);
   out.close();
   System.out.println(new File("result.obj").length());
 
   ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
           "result.obj"));
   //從文件依次讀出兩個(gè)文件
   Test t1 = (Test) oin.readObject();
   Test t2 = (Test) oin.readObject();
   oin.close();
            
   //判斷兩個(gè)引用是否指向同一個(gè)對(duì)象
   System.out.println(t1 == t2);

清單 3 中對(duì)同一對(duì)象兩次寫(xiě)入文件,打印出寫(xiě)入一次對(duì)象后的存儲(chǔ)大小和寫(xiě)入兩次后的存儲(chǔ)大小,然后從文件中反序列化出兩個(gè)對(duì)象,比較這兩個(gè)對(duì)象是否為同一對(duì)象。一般的思維是,兩次寫(xiě)入對(duì)象,文件大小會(huì)變?yōu)閮杀兜拇笮?,反序列化時(shí),由于從文件讀取,生成了兩個(gè)對(duì)象,判斷相等時(shí)應(yīng)該是輸入 false 才對(duì),但是最后結(jié)果輸出如圖 4 所示。
圖 4. 示例程序輸出
圖 4. 示例程序輸出
我們看到,第二次寫(xiě)入對(duì)象時(shí)文件只增加了 5 字節(jié),并且兩個(gè)對(duì)象是相等的,這是為什么呢?
解答:Java 序列化機(jī)制為了節(jié)省磁盤(pán)空間,具有特定的存儲(chǔ)規(guī)則,當(dāng)寫(xiě)入文件的為同一對(duì)象時(shí),并不會(huì)再將對(duì)象的內(nèi)容進(jìn)行存儲(chǔ),而只是再次存儲(chǔ)一份引用,上面增加的 5 字節(jié)的存儲(chǔ)空間就是新增引用和一些控制信息的空間。反序列化時(shí),恢復(fù)引用關(guān)系,使得清單 3 中的 t1 和 t2 指向唯一的對(duì)象,二者相等,輸出 true。該存儲(chǔ)規(guī)則極大的節(jié)省了存儲(chǔ)空間。

特性案例分析

查看清單 5 的代碼。
清單 5. 案例代碼

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);

清單 4 的目的是希望將 test 對(duì)象兩次保存到 result.obj 文件中,寫(xiě)入一次以后修改對(duì)象屬性值再次保存第二次,然后從 result.obj 中再依次讀出兩個(gè)對(duì)象,輸出這兩個(gè)對(duì)象的 i 屬性值。案例代碼的目的原本是希望一次性傳輸對(duì)象修改前后的狀態(tài)。
結(jié)果兩個(gè)輸出的都是 1, 原因就是第一次寫(xiě)入對(duì)象以后,第二次再試圖寫(xiě)的時(shí)候,虛擬機(jī)根據(jù)引用關(guān)系知道已經(jīng)有一個(gè)相同對(duì)象已經(jīng)寫(xiě)入文件,因此只保存第二次寫(xiě)的引用,所以讀取時(shí),都是第一次保存的對(duì)象。讀者在使用一個(gè)文件多次 writeObject 需要特別注意這個(gè)問(wèn)題。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/67875.html

相關(guān)文章

  • 小伙子,你真的搞懂 transient 關(guān)鍵字了嗎?

    摘要:由以上結(jié)果分析可知,靜態(tài)變量不能被序列化,示例讀取出來(lái)的是在內(nèi)存中存儲(chǔ)的值。關(guān)鍵字總結(jié)修飾的變量不能被序列化只作用于實(shí)現(xiàn)接口只能用來(lái)修飾普通成員變量字段不管有沒(méi)有修飾,靜態(tài)變量都不能被序列化好了,棧長(zhǎng)花了半天時(shí)間,終于整理完了。 先解釋下什么是序列化 我們的對(duì)象并不只是存在內(nèi)存中,還需要傳輸網(wǎng)絡(luò),或者保存起來(lái)下次再加載出來(lái)用,所以需要Java序列化技術(shù)。 Java序列化技術(shù)正是將對(duì)象轉(zhuǎn)...

    curlyCheng 評(píng)論0 收藏0
  • Java 對(duì)象列化

    摘要:對(duì)象序列化對(duì)象序列化機(jī)制允許把內(nèi)存中的對(duì)象轉(zhuǎn)換成與平臺(tái)無(wú)關(guān)的二進(jìn)制流,從而可以保存到磁盤(pán)或者進(jìn)行網(wǎng)絡(luò)傳輸,其它程序獲得這個(gè)二進(jìn)制流后可以將其恢復(fù)成原來(lái)的對(duì)象。 對(duì)象序列化 對(duì)象序列化機(jī)制允許把內(nèi)存中的Java對(duì)象轉(zhuǎn)換成與平臺(tái)無(wú)關(guān)的二進(jìn)制流,從而可以保存到磁盤(pán)或者進(jìn)行網(wǎng)絡(luò)傳輸,其它程序獲得這個(gè)二進(jìn)制流后可以將其恢復(fù)成原來(lái)的Java對(duì)象。 序列化機(jī)制可以使對(duì)象可以脫離程序的運(yùn)行而對(duì)立存在 ...

    tianyu 評(píng)論0 收藏0
  • Java開(kāi)發(fā)中對(duì)象的列化與反列化

    摘要:在中,對(duì)象的序列化與反序列化被廣泛應(yīng)用到遠(yuǎn)程方法調(diào)用及網(wǎng)絡(luò)傳輸中。相關(guān)接口及類(lèi)為了方便開(kāi)發(fā)人員將對(duì)象進(jìn)行序列化及反序列化提供了一套方便的來(lái)支持。未實(shí)現(xiàn)此接口的類(lèi)將無(wú)法使其任何狀態(tài)序列化或反序列化。 序列化與反序列化 序列化 (Serialization)是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^(guò)程。一般將一個(gè)對(duì)象存儲(chǔ)至一個(gè)儲(chǔ)存媒介,例如檔案或是記億體緩沖等。在網(wǎng)絡(luò)傳輸過(guò)程中,可以...

    fox_soyoung 評(píng)論0 收藏0
  • Java列化

    摘要:的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)象的數(shù)據(jù),有關(guān)對(duì)象的類(lèi)型信息和存儲(chǔ)在對(duì)象中的數(shù)據(jù)類(lèi)型。任何實(shí)現(xiàn)了接口的類(lèi)都可以被序列化。一旦對(duì)象被序列化或者重新裝配,就會(huì)分別調(diào)用那兩個(gè)方法。 Java序列化 1. 什么是序列化? 序列化是將一個(gè)對(duì)象的狀態(tài),各屬性的值序列化保存起來(lái),然后在合適的時(shí)候通過(guò)反序列化獲得。 Java的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)象...

    lbool 評(píng)論0 收藏0
  • 淺談Java列化

    摘要:的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)象的數(shù)據(jù),有關(guān)對(duì)象的類(lèi)型信息和存儲(chǔ)在對(duì)象中的數(shù)據(jù)類(lèi)型。這個(gè)是根據(jù)類(lèi)名接口名成員方法及屬性等來(lái)生成一個(gè)位的哈希字段,因?yàn)樵黾恿俗侄?,因此生成的不一樣了? Java序列化 什么是序列化? 序列化是將一個(gè)對(duì)象的狀態(tài),各屬性的值序列化保存起來(lái),然后在合適的時(shí)候通過(guò)反序列化獲得。 Java的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)...

    winterdawn 評(píng)論0 收藏0
  • java列化和反列化說(shuō)起

    摘要:從的序列化和反序列化說(shuō)起序列化是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^(guò)程,而相反的過(guò)程就稱(chēng)為反序列化。當(dāng)使用接口來(lái)進(jìn)行序列化與反序列化的時(shí)候需要開(kāi)發(fā)人員重寫(xiě)與方法。 從java的序列化和反序列化說(shuō)起 序列化 (Serialization)是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^(guò)程,而相反的過(guò)程就稱(chēng)為反序列化。 在java中允許我們創(chuàng)建可復(fù)用的對(duì)象,但是這些對(duì)象僅僅存在j...

    whlong 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

GeekQiaQia

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<