工作中,我們經(jīng)常聽到序列化
和反序列化
,那么,什么是序列化
?什么又是反序列化
?這篇文章,我們來分析一個(gè)招商的面試題:為什么需要序列化
和反序列化
?
1. 什么是序列化和反序列化?
簡單來說,序列化
就是把一個(gè)Java對象轉(zhuǎn)換成一系列字節(jié)的過程,這些字節(jié)可以被存儲到文件、數(shù)據(jù)庫,或者通過網(wǎng)絡(luò)傳輸。反過來,反序列化
則是把這些字節(jié)重新轉(zhuǎn)換成Java對象的過程。
想象一下,你有一個(gè)手機(jī)應(yīng)用中的用戶對象(比如用戶的名字、年齡等信息)。如果你想將這個(gè)用戶對象存儲起來,或者發(fā)送給服務(wù)器,你就需要先序列化它。等到需要使用的時(shí)候,再通過反序列化把它恢復(fù)成原來的對象。
?
2. 為什么需要序列化?
“為什么需要序列化?為什么不能直接使用對象呢?”這確實(shí)是一個(gè)好問題,而且很多工作多年的程序員不一定能回答清楚。綜合來看:需要序列化的主要原因有以下三點(diǎn):
- 持久化存儲:當(dāng)你需要將對象的數(shù)據(jù)保存到磁盤或數(shù)據(jù)庫中時(shí),必須把對象轉(zhuǎn)換成一系列字節(jié)。
- 網(wǎng)絡(luò)傳輸:在分布式系統(tǒng)中,不同的機(jī)器需要交換對象數(shù)據(jù),序列化是實(shí)現(xiàn)這一點(diǎn)的關(guān)鍵。
- 深拷貝:有時(shí)候需要創(chuàng)建對象的副本,序列化和反序列化可以幫助你實(shí)現(xiàn)深拷貝。
更直白的說,序列化是為了實(shí)現(xiàn)持久化和網(wǎng)絡(luò)傳輸,對象是應(yīng)用層的東西,不同的語言(比如:java,go,python)創(chuàng)建的對象還不一樣,實(shí)現(xiàn)持久化和網(wǎng)絡(luò)傳輸?shù)妮d體不認(rèn)這些對象。
3. 序列化的原理分析
Java中的序列化是通過實(shí)現(xiàn)java.io.Serializable
接口來實(shí)現(xiàn)的。這個(gè)接口是一個(gè)標(biāo)記接口,意味著它本身沒有任何方法,只是用來標(biāo)記這個(gè)類的對象是可序列化的。
當(dāng)你序列化一個(gè)對象時(shí),Java會將對象的所有非瞬態(tài)(transient
)和非靜態(tài)字段的值轉(zhuǎn)換成字節(jié)流。這包括對象的基本數(shù)據(jù)類型、引用類型,甚至是繼承自父類的字段。
序列化的步驟
- 實(shí)現(xiàn)
Serializable
接口:你的類需要實(shí)現(xiàn)這個(gè)接口。 - **創(chuàng)建
ObjectOutputStream
**:用于將對象轉(zhuǎn)換成字節(jié)流。 - 調(diào)用
writeObject
方法:將對象寫入輸出流。 - 關(guān)閉流:別忘了關(guān)閉流以釋放資源。
反序列化的步驟大致相同,只不過是使用ObjectInputStream
和readObject
方法。
4. 示例演示
讓我們通過一個(gè)簡單的例子來看看實(shí)際操作是怎樣的。
定義一個(gè)可序列化的類
import java.io.Serializable;
publicclass User implements Serializable {
privatestaticfinallong serialVersionUID = 1L; // 推薦定義序列化版本號
private String name;
privateint age;
privatetransient String password; // transient字段不會被序列化
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
// 省略getter和setter方法
@Override
public String toString() {
return"User{name='" + name + "', age=" + age + ", password='" + password + "'}";
}
}
序列化對象
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
publicclass SerializeDemo {
public static void main(String[] args) {
User user = new User("Alice", 30, "secret123");
try (FileOutputStream fileOut = new FileOutputStream("user.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(user);
System.out.println("對象已序列化到 user.ser 文件中.");
} catch (IOException i) {
i.printStackTrace();
}
}
}
運(yùn)行上述代碼后,你會發(fā)現(xiàn)當(dāng)前目錄下生成了一個(gè)名為user.ser
的文件,這就是序列化后的字節(jié)流。
反序列化對象
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
publicclass DeserializeDemo {
public static void main(String[] args) {
User user = null;
try (FileInputStream fileIn = new FileInputStream("user.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
user = (User) in.readObject();
System.out.println("反序列化后的對象: " + user);
} catch (IOException | ClassNotFoundException i) {
i.printStackTrace();
}
}
}
運(yùn)行這段代碼,你會看到輸出:
反序列化后的對象: User{name='Alice', age=30, password='null'}
注意到password
字段為空,這是因?yàn)樗宦暶鳛?/span>transient
,在序列化過程中被忽略了。
5. 常見問題與注意事項(xiàng)
serialVersionUID是干嘛的?
serialVersionUID
是序列化時(shí)用來驗(yàn)證版本兼容性的一個(gè)標(biāo)識符。如果你不顯式定義它,Java會根據(jù)類的結(jié)構(gòu)自動生成。但為了避免類結(jié)構(gòu)變化導(dǎo)致序列化失敗,建議手動定義一個(gè)固定的值。
繼承關(guān)系中的序列化
如果一個(gè)類的父類沒有實(shí)現(xiàn)Serializable
接口,那么在序列化子類對象時(shí),父類的字段不會被序列化。反序列化時(shí),父類的構(gòu)造函數(shù)會被調(diào)用初始化父類部分。
處理敏感信息
使用transient
關(guān)鍵字可以防止敏感信息被序列化,比如密碼字段。此外,你也可以自定義序列化邏輯,通過實(shí)現(xiàn)writeObject
和readObject
方法來更精細(xì)地控制序列化過程。
6. 總結(jié)
本文,我們深入淺出地探討了Java中的序列化和反序列化,從基本概念到原理分析,再到實(shí)際的代碼示例,希望你對這兩個(gè)重要的技術(shù)點(diǎn)有了更清晰的理解。
為什么需要序列化和反序列化?
最直白的說,如果不進(jìn)行持久化和網(wǎng)絡(luò)傳輸,根本不需要序列化和反序列化。如果需要實(shí)現(xiàn)持久化和網(wǎng)絡(luò)傳輸,就必須序列化和反序列化,因?yàn)閷ο笫菓?yīng)用層的東西,不同的語言(比如:java,go,python)創(chuàng)建的對象還不一樣,實(shí)現(xiàn)持久化和網(wǎng)絡(luò)傳輸?shù)妮d體根本不認(rèn)這些對象。
閱讀原文:原文鏈接
該文章在 2025/5/6 12:12:09 編輯過