中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java序列化的示例分析

發布時間:2021-07-22 16:07:04 來源:億速云 閱讀:93 作者:小新 欄目:編程語言

這篇文章主要為大家展示了“java序列化的示例分析”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“java序列化的示例分析”這篇文章吧。

正文

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

序列化 ID 問題

情境:兩個客戶端 A 和 B 試圖通過網絡傳遞對象數據,A 端將對象 C 序列化為二進制數據再傳給 B,B 反序列化得到 C。

問題:C 對象的全類路徑假設為 com.inout.Test,在 A 和 B 端都有這么一個類文件,功能代碼完全一致。也都實現了 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,一個是隨機生成一個不重復的 long 類型數據(實際上是使用 JDK 工具生成),在這里有一個建議,如果沒有特殊需求,就是用默認的 1L 就可以,這樣可以確保代碼一致時反序列化成功。那么隨機生成的序列化 ID 有什么作用呢,有些時候,通過改變序列化 ID 可以用來限制某些用戶的使用。

靜態變量序列化

清單 2. 靜態變量序列化問題代碼

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

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

對敏感字段加密

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

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

清單 3. 靜態變量序列化問題代碼

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 進行解密,只有擁有密鑰的客戶端,才可以正確的解析出密碼,確保了數據的安全。執行控制臺輸出如圖所示。

java序列化的示例分析

特性使用案例

RMI 技術是完全基于 Java 序列化技術的,服務器端接口調用所需要的參數對象來至于客戶端,它們通過網絡相互傳輸。這就涉及 RMI 的安全傳輸的問題。一些敏感的字段,如用戶名密碼(用戶登錄時需要對密碼進行傳輸),我們希望對其進行加密,這時,就可以采用本節介紹的方法在客戶端對密碼進行加密,服務器端進行解密,確保數據傳輸的安全性。

序列化存儲規則

清單 4. 存儲規則問題代碼

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 中對同一對象兩次寫入文件,打印出寫入一次對象后的存儲大小和寫入兩次后的存儲大小,然后從文件中反序列化出兩個對象,比較這兩個對象是否為同一對象。一般的思維是,兩次寫入對象,文件大小會變為兩倍的大小,反序列化時,由于從文件讀取,生成了兩個對象,判斷相等時應該是輸入 false 才對,但是最后結果輸出如圖所示。

31
36
true

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

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

特性案例分析

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

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

以上是“java序列化的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

田林县| 阿鲁科尔沁旗| 来安县| 浮山县| 高安市| 乌兰察布市| 广州市| 德惠市| 清涧县| 神农架林区| 嘉禾县| 卓资县| 南宁市| 旬邑县| 九台市| 永顺县| 中宁县| 平陆县| 永济市| 绥滨县| 鱼台县| 临潭县| 鸡泽县| 玉门市| 墨玉县| 綦江县| 济阳县| 阿拉善盟| 昌平区| 亚东县| 原阳县| 牡丹江市| 斗六市| 益阳市| 博客| 巧家县| 松溪县| 木兰县| 阆中市| 和平区| 永安市|