国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Java 序列化的高級認識

Java 序列化的高級認識

紅薯發(fā)表于 3-20 16:13 3個月前,6回/501閱,最后回答: 2天前 (21人收藏,收藏|舉報)

簡介: 如果你只知道實現(xiàn) Serializable接口的對象,可以序列化為本地文件。那你最好再閱讀該篇文章,文章對序列化進行了更深一步的討論,用實際的例子代碼講述了序列化的高級認識,包括父類序列化的問題、靜態(tài)變量問題、transient 關(guān)鍵字的影響、序列化 ID問題。在筆者實際開發(fā)過程中,就多次遇到序列化的問題,在該文章中也會與讀者分享。

引言

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

文章結(jié)構(gòu)

本文將逐一的介紹幾個情境,順序如下面的列表。

  • 序列化 ID 的問題
  • 靜態(tài)變量序列化
  • 父類的序列化與 Transient 關(guān)鍵字
  • 對敏感字段加密
  • 序列化存儲規(guī)則

列表的每一部分講述了一個單獨的情境,讀者可以分別查看。

序列化 ID 問題

情境:兩個客戶端 A 和 B 試圖通過網(wǎng)絡(luò)傳遞對象數(shù)據(jù),A 端將對象 C 序列化為二進制數(shù)據(jù)再傳給 B,B 反序列化得到 C。

問題:C 對象的全類路徑假設(shè)為 com.inout.Test,在 A 和 B 端都有這么一個類文件,功能代碼完全一致。也都實現(xiàn)了 Serializable 接口,但是反序列化時總是提示不成功。

解決虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清單 1 中,雖然兩個類的功能代碼完全一致,但是序列化 ID 不同,他們無法相互序列化和反序列化。


清單 1. 相同功能代碼不同序列化 ID 的類對比

            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 下提供了兩種生成策略,一個是固定的 1L,一個是隨機生成一個不重復(fù)的 long 類型數(shù)據(jù)(實際上是使用JDK 工具生成),在這里有一個建議,如果沒有特殊需求,就是用默認的 1L 就可以,這樣可以確保代碼一致時反序列化成功。那么隨機生成的序列化ID 有什么作用呢,有些時候,通過改變序列化 ID 可以用來限制某些用戶的使用。

特性使用案例

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


圖 1. 案例程序結(jié)構(gòu)

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

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

情境:查看清單 2 的代碼。


清單 2. 靜態(tài)變量序列化問題代碼

            public class Test implements Serializable {            private static final long serialVersionUID = 1L;            public static int staticVar = 5;            public static void main(String[] args) {            try {            //初始時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();            //再讀取,通過t.staticVar打印新的值            System.out.println(t.staticVar);            } catch (FileNotFoundException e) {            e.printStackTrace();            } catch (IOException e) {            e.printStackTrace();            } catch (ClassNotFoundException e) {            e.printStackTrace();            }            }            }            

 

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

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

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

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

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

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

特性使用案例

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


圖 2. 案例程序類圖

上圖中可以看出,attr1、attr2、attr3、attr5 都不會被序列化,放在父類中的好處在于當(dāng)有另外一個 Child 類時,attr1、attr2、attr3 依然不會被序列化,不用重復(fù)抒寫 transient,代碼簡潔。

對敏感字段加密

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

解決:在序列化過程中,虛擬機會試圖調(diào)用對象類里的 writeObject 和 readObject方法,進行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認調(diào)用是 ObjectOutputStream 的defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的writeObject 和 readObject方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態(tài)改變序列化的數(shù)值?;谶@個原理,可以在實際應(yīng)用中得到使用,用于敏感字段的加密工作,清單 3 展示了這個過程。


清單 2. 靜態(tài)變量序列化問題代碼

            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 方法中,對密碼進行了加密,在 readObject 中則對 password 進行解密,只有擁有密鑰的客戶端,才可以正確的解析出密碼,確保了數(shù)據(jù)的安全。執(zhí)行清單 3 后控制臺輸出如圖 3 所示。


圖 3. 數(shù)據(jù)加密演示

特性使用案例

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

序列化存儲規(guī)則

情境:問題代碼如清單 3 所示。


清單 3. 存儲規(guī)則問題代碼

            ObjectOutputStream out = new ObjectOutputStream(            new FileOutputStream("result.obj"));            Test test = new Test();            //試圖將對象兩次寫入文件            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"));            //從文件依次讀出兩個文件            Test t1 = (Test) oin.readObject();            Test t2 = (Test) oin.readObject();            oin.close();            //判斷兩個引用是否指向同一個對象            System.out.println(t1 == t2);            

 

清單 3中對同一對象兩次寫入文件,打印出寫入一次對象后的存儲大小和寫入兩次后的存儲大小,然后從文件中反序列化出兩個對象,比較這兩個對象是否為同一對象。一般的思維是,兩次寫入對象,文件大小會變?yōu)閮杀兜拇笮?,反序列化時,由于從文件讀取,生成了兩個對象,判斷相等時應(yīng)該是輸入 false才對,但是最后結(jié)果輸出如圖 4 所示。


圖 4. 示例程序輸出

我們看到,第二次寫入對象時文件只增加了 5 字節(jié),并且兩個對象是相等的,這是為什么呢?

解答:Java序列化機制為了節(jié)省磁盤空間,具有特定的存儲規(guī)則,當(dāng)寫入文件的為同一對象時,并不會再將對象的內(nèi)容進行存儲,而只是再次存儲一份引用,上面增加的 5字節(jié)的存儲空間就是新增引用和一些控制信息的空間。反序列化時,恢復(fù)引用關(guān)系,使得清單 3 中的 t1 和 t2 指向唯一的對象,二者相等,輸出true。該存儲規(guī)則極大的節(jié)省了存儲空間。

特性案例分析

查看清單 4 的代碼。


清單 4. 案例代碼

            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 對象兩次保存到 result.obj 文件中,寫入一次以后修改對象屬性值再次保存第二次,然后從result.obj 中再依次讀出兩個對象,輸出這兩個對象的 i 屬性值。案例代碼的目的原本是希望一次性傳輸對象修改前后的狀態(tài)。

結(jié)果兩個輸出的都是 1,原因就是第一次寫入對象以后,第二次再試圖寫的時候,虛擬機根據(jù)引用關(guān)系知道已經(jīng)有一個相同對象已經(jīng)寫入文件,因此只保存第二次寫的引用,所以讀取時,都是第一次保存的對象。讀者在使用一個文件多次 writeObject 需要特別注意這個問題。

小結(jié)

本文通過幾個具體的情景,介紹了 Java序列化的一些高級知識,雖說高級,并不是說讀者們都不了解,希望用筆者介紹的情景讓讀者加深印象,能夠更加合理的利用 Java序列化技術(shù),在未來開發(fā)之路上遇到序列化問題時,可以及時的解決。由于本人知識水平有限,文章中倘若有錯誤的地方,歡迎聯(lián)系我批評指正。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
C# 特性(Attribute)之Serializable特性
在C#中的作用-NET 中的對象序列化
Java程序員必備:序列化全方位解析
Java 序列化深入分析
講真,下次打死我也不敢隨便改serialVersionUID了
序列化與反序列化,使用中千萬要避開這些坑
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服