最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

通過(guò)實(shí)例深入了解java序列化

 更新時(shí)間:2019年06月18日 10:25:05   作者:chen_hao  
這篇文章主要介紹了通過(guò)實(shí)例深入了解java序列化,

正文

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

序列化 ID 問(wèn)題

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

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

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

清單 1. 相同功能代碼不同序列化 ID 的類對(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 類型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成),在這里有一個(gè)建議,如果沒有特殊需求,就是用默認(rèn)的 1L 就可以,這樣可以確保代碼一致時(shí)反序列化成功。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候,通過(guò)改變序列化 ID 可以用來(lái)限制某些用戶的使用。

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

清單 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)變量屬于類的狀態(tài),因此 序列化并不保存靜態(tài)變量。

對(duì)敏感字段加密

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

解決:在序列化過(guò)程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過(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)行解密,只有擁有密鑰的客戶端,才可以正確的解析出密碼,確保了數(shù)據(jù)的安全。執(zhí)行控制臺(tái)輸出如圖所示。

特性使用案例

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

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

清單 4. 存儲(chǔ)規(guī)則問(wèn)題代碼

ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
Test test = new Test();
//試圖將對(duì)象兩次寫入文件
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ì)象兩次寫入文件,打印出寫入一次對(duì)象后的存儲(chǔ)大小和寫入兩次后的存儲(chǔ)大小,然后從文件中反序列化出兩個(gè)對(duì)象,比較這兩個(gè)對(duì)象是否為同一對(duì)象。一般的思維是,兩次寫入對(duì)象,文件大小會(huì)變?yōu)閮杀兜拇笮?,反序列化時(shí),由于從文件讀取,生成了兩個(gè)對(duì)象,判斷相等時(shí)應(yīng)該是輸入 false 才對(duì),但是最后結(jié)果輸出如圖所示。

31
36
true

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

解答:Java 序列化機(jī)制為了節(jié)省磁盤空間,具有特定的存儲(chǔ)規(guī)則,當(dāng)寫入文件的為同一對(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. 案例代碼

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

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

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

小結(jié)

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Springboot應(yīng)用gradle?Plugin示例詳解

    Springboot應(yīng)用gradle?Plugin示例詳解

    這篇文章主要介紹了Springboot應(yīng)用gradle?Plugin詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • spring boot 部署為jar包的方法示例

    spring boot 部署為jar包的方法示例

    本篇文章主要介紹了spring boot 部署為jar的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10
  • Java?中向?Arraylist?添加對(duì)象的示例代碼

    Java?中向?Arraylist?添加對(duì)象的示例代碼

    本文介紹了如何在 Java 中向 ArrayList 添加對(duì)象,并提供了示例和注意事項(xiàng),通過(guò)掌握這些知識(shí),讀者可以在自己的 Java 項(xiàng)目中有效地使用 ArrayList 來(lái)存儲(chǔ)和操作對(duì)象,需要的朋友可以參考下
    2023-11-11
  • Java算法之?dāng)?shù)組冒泡排序代碼實(shí)例講解

    Java算法之?dāng)?shù)組冒泡排序代碼實(shí)例講解

    這篇文章主要介紹了Java算法之?dāng)?shù)組冒泡排序代碼實(shí)例講解,文中用代碼舉例講解的很清晰,有感興趣的同學(xué)可以研究下
    2021-03-03
  • springboot整合cxf發(fā)布webservice以及調(diào)用的方法

    springboot整合cxf發(fā)布webservice以及調(diào)用的方法

    這篇文章主要介紹了springboot整合cxf發(fā)布webservice以及調(diào)用的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • 淺談SpringMVC國(guó)際化支持

    淺談SpringMVC國(guó)際化支持

    這篇文章主要介紹了淺談SpringMVC國(guó)際化支持,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • Java 隨機(jī)生成任意組電話號(hào)碼過(guò)程解析

    Java 隨機(jī)生成任意組電話號(hào)碼過(guò)程解析

    這篇文章主要介紹了Java 隨機(jī)生成任意組電話號(hào)碼過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • Mybatis中的延遲加載,以及原理分析

    Mybatis中的延遲加載,以及原理分析

    這篇文章主要介紹了Mybatis中的延遲加載以及原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • java數(shù)據(jù)庫(kù)開發(fā)之JDBC的完整封裝兼容多種數(shù)據(jù)庫(kù)

    java數(shù)據(jù)庫(kù)開發(fā)之JDBC的完整封裝兼容多種數(shù)據(jù)庫(kù)

    這篇文章主要介紹了java數(shù)據(jù)庫(kù)開發(fā)之JDBC的完整封裝兼容多種數(shù)據(jù)庫(kù),需要的朋友可以參考下
    2020-02-02
  • Netty通道的容器屬性Attribute詳解

    Netty通道的容器屬性Attribute詳解

    這篇文章主要介紹了Netty通道的容器屬性Attribute詳解,Netty中的Channel通道類,有類似于Map的容器功能,可以通過(guò)鍵值對(duì)的形式來(lái)保存任何Java Object的值,一般來(lái)說(shuō)可以存放一些與通道實(shí)例相關(guān)聯(lián)的屬性,比如說(shuō)服務(wù)期端的ServerSession會(huì)話實(shí)例,需要的朋友可以參考下
    2023-12-12

最新評(píng)論

北辰区| 贵港市| 佛学| 上杭县| 离岛区| 九龙县| 鸡东县| 鄂州市| 安仁县| 石城县| 遂川县| 成都市| 桂平市| 高安市| 德惠市| 太湖县| 东乌| 澄江县| 峨眉山市| 扶绥县| 祁东县| 芦溪县| 高台县| 绍兴市| 红原县| 孟州市| 北安市| 壤塘县| 广灵县| 淮阳县| 南开区| 万年县| 大理市| 通化县| 梁山县| 泉州市| 克拉玛依市| 固镇县| 阿克陶县| 大田县| 长葛市|