Java中對象的轉(zhuǎn)移之序列化與反序列化方式
1. 序列化與反序列化的定義與實現(xiàn)流程
序列化(Serialization)和反序列化(Deserialization)是處理對象持久化和網(wǎng)絡(luò)傳輸?shù)年P(guān)鍵技術(shù)。它們在分布式系統(tǒng)、緩存系統(tǒng)和跨應(yīng)用傳輸數(shù)據(jù)中應(yīng)用廣泛。
序列化是什么?
序列化是將一個對象轉(zhuǎn)換成字節(jié)流的過程,這樣的數(shù)據(jù)形式便于存儲到文件、數(shù)據(jù)庫,或通過網(wǎng)絡(luò)傳輸?shù)狡渌到y(tǒng)。簡單來說,序列化就是把數(shù)據(jù)打包成一個可轉(zhuǎn)移、存儲的“快遞包裹”。
反序列化是什么?
反序列化是序列化的逆過程,即將字節(jié)流還原為對象的過程。通過反序列化,系統(tǒng)可以讀取存儲的對象數(shù)據(jù),或通過網(wǎng)絡(luò)傳輸接收到的數(shù)據(jù)重構(gòu)出對象。
序列化的實現(xiàn)流程
序列化和反序列化的基本流程可以分為以下幾步:
對象狀態(tài)轉(zhuǎn)換為字節(jié)流:
- 將對象中的屬性、數(shù)據(jù)結(jié)構(gòu)和標(biāo)識符等信息轉(zhuǎn)換成一組字節(jié),形成一個流。
- 這個流包含了該對象的類信息以及它的字段值,使得接收方能夠準(zhǔn)確地還原對象。
傳輸或存儲字節(jié)流:
- 將得到的字節(jié)流傳輸?shù)侥繕?biāo)位置(例如:遠(yuǎn)程服務(wù)器)或存儲到介質(zhì)中(如文件系統(tǒng)、數(shù)據(jù)庫),以便后續(xù)的訪問或復(fù)用。
將字節(jié)流轉(zhuǎn)為對象(反序列化):
- 當(dāng)需要讀取對象時,字節(jié)流會被重新轉(zhuǎn)換回對象,這時會從流中解析出類信息及字段數(shù)據(jù),恢復(fù)對象的完整狀態(tài)。
這種流程實現(xiàn)了對象在不同系統(tǒng)、不同時刻的持久化保存和跨網(wǎng)絡(luò)、跨進(jìn)程的數(shù)據(jù)傳輸。序列化的技術(shù)不僅限于Java,在很多語言和框架中都有類似實現(xiàn),適用于不同的應(yīng)用需求。
2. Java中的序列化與反序列化實現(xiàn)
Java 提供了內(nèi)置的序列化工具,使對象能方便地轉(zhuǎn)換為字節(jié)流。主要工具包括ObjectOutputStream和ObjectInputStream,以及Serializable和Externalizable接口。
1.Serializable接口:標(biāo)識對象可序列化
要實現(xiàn)序列化的對象類需要實現(xiàn)Serializable接口。這是一個標(biāo)記接口,也就是不包含方法,只是標(biāo)記該類可以序列化。
示例:
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 省略getters和setters
}
解釋: 在這個例子中,Person類實現(xiàn)了Serializable接口,說明可以把Person對象轉(zhuǎn)成字節(jié)流。類中包含的所有字段都會在序列化過程中轉(zhuǎn)換成字節(jié)流。
2.ObjectOutputStream和ObjectInputStream:讀寫字節(jié)流的對象
ObjectOutputStream:用來寫入對象的字節(jié)流,執(zhí)行序列化。ObjectInputStream:用來讀取對象的字節(jié)流,執(zhí)行反序列化。
示例:序列化對象
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class SerializeExample {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
oos.writeObject(person); // 將Person對象寫入文件
System.out.println("對象已序列化");
} catch (Exception e) {
e.printStackTrace();
}
}
}
解釋:
- 創(chuàng)建
ObjectOutputStream,用于將對象寫入文件。 - 使用
writeObject方法將person對象轉(zhuǎn)為字節(jié)流寫入文件person.ser中。 - 文件中保存了
person對象的狀態(tài)數(shù)據(jù),以便后續(xù)讀取。
示例:反序列化對象
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializeExample {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject(); // 從文件中讀取對象
System.out.println("對象已反序列化: " + person.getName() + ", " + person.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}
解釋:
- 創(chuàng)建
ObjectInputStream,讀取存儲的字節(jié)流。 - 使用
readObject方法從字節(jié)流中還原對象。 - 將反序列化的
person對象信息打印出來。
3.transient關(guān)鍵字:保護(hù)敏感數(shù)據(jù)
在某些場景中,可能不希望某些字段被序列化,例如密碼、敏感信息等。可以用transient關(guān)鍵字聲明這些字段,以忽略序列化。
示例:
public class User implements Serializable {
private String username;
private transient String password; // 不會被序列化
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
在這個例子中,password字段不會被序列化,也不會被寫入字節(jié)流中,從而保護(hù)了敏感數(shù)據(jù)。
4.Externalizable接口:自定義序列化邏輯
除了Serializable,Java還提供了Externalizable接口,允許完全自定義序列化過程。實現(xiàn)Externalizable接口的類需要覆蓋兩個方法:
writeExternal(ObjectOutput out):定義如何序列化對象。readExternal(ObjectInput in):定義如何反序列化對象。
示例:
import java.io.Externalizable;
import java.io.ObjectOutput;
import java.io.ObjectInput;
public class Book implements Externalizable {
private String title;
private double price;
public Book() {} // 必須有無參構(gòu)造方法
public Book(String title, double price) {
this.title = title;
this.price = price;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(title);
out.writeDouble(price);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
title = in.readUTF();
price = in.readDouble();
}
}
解釋:
writeExternal方法中定義了如何把title和price字段寫入字節(jié)流。readExternal方法中定義了從字節(jié)流讀取數(shù)據(jù)的順序和方式。- 這種方式比
Serializable更靈活,可以完全控制序列化的細(xì)節(jié)。
在Java中,序列化和反序列化的基本實現(xiàn)方式非常直接,Serializable接口和ObjectOutputStream/ObjectInputStream提供了便捷的工具。對于更復(fù)雜的需求,Externalizable接口可以讓開發(fā)者自定義序列化和反序列化的過程。這些方法不僅在Java內(nèi)使用方便,同時為跨網(wǎng)絡(luò)傳輸、存儲等應(yīng)用場景提供了基礎(chǔ)支持。
3. Java序列化的缺點和替代方案
Java原生序列化是一種方便的方式,特別適合在同一系統(tǒng)中使用。不過,它在實際應(yīng)用中存在以下明顯的缺點:
Java序列化的缺點
性能較差:
- Java序列化默認(rèn)包含了很多額外的元數(shù)據(jù)(比如類信息),導(dǎo)致生成的字節(jié)流文件偏大。序列化和反序列化速度較慢,尤其在大規(guī)模數(shù)據(jù)傳輸中更明顯。
- 這種速度較慢的序列化方式在處理大量數(shù)據(jù)、需要頻繁傳輸?shù)姆植际较到y(tǒng)中效率不足。
不跨語言:
- Java原生序列化生成的二進(jìn)制格式只適用于Java平臺。也就是說,其他編程語言無法直接解析Java序列化生成的字節(jié)流。因此,如果系統(tǒng)需要與其他語言(例如C++、Python)交互,就不適用Java原生序列化。
存在安全漏洞:
- Java序列化容易受到“反序列化攻擊”的威脅。惡意用戶可以構(gòu)造特定的序列化數(shù)據(jù)流,在反序列化過程中觸發(fā)惡意代碼執(zhí)行。這種攻擊常見于接受外部輸入的系統(tǒng),安全風(fēng)險很大。
- 由于這種安全隱患,Java原生序列化通常不適合在公開的API或網(wǎng)絡(luò)傳輸中使用。
缺乏版本控制:
- 當(dāng)類的結(jié)構(gòu)發(fā)生改變(例如添加或移除字段)時,Java的序列化和反序列化很容易出現(xiàn)兼容性問題。例如,老版本的對象在字段缺失時可能導(dǎo)致反序列化失敗。
- 盡管可以通過
serialVersionUID標(biāo)識版本,但在實際應(yīng)用中維護(hù)起來仍然較麻煩。
這些缺點在很多分布式、跨平臺系統(tǒng)中都是阻礙,因此現(xiàn)在很多開發(fā)者會考慮使用更高效、安全、靈活的序列化框架。下面介紹兩個主流替代方案:FastJson和Protobuf。
替代方案一:FastJson
FastJson是阿里巴巴開發(fā)的一個高性能JSON庫,主要用于Java對象和JSON格式之間的相互轉(zhuǎn)換。JSON格式是一種人類可讀的數(shù)據(jù)格式,可以方便地在不同語言間共享。
FastJson的優(yōu)勢:
跨平臺:
- JSON是一種輕量級的數(shù)據(jù)交換格式,幾乎每種編程語言都支持。因此,使用FastJson序列化的對象能夠輕松地在不同編程語言之間傳遞。
速度快,性能優(yōu)異:
- FastJson專為速度和性能優(yōu)化,在Java中使用非常高效。它支持流式解析、緩存等多種優(yōu)化方式,在大數(shù)據(jù)量處理上比Java原生序列化性能更佳。
支持靈活的數(shù)據(jù)結(jié)構(gòu):
- FastJson序列化的數(shù)據(jù)格式(JSON)具有良好的兼容性,特別適合于動態(tài)數(shù)據(jù)結(jié)構(gòu)和頻繁變化的數(shù)據(jù)格式。即便類的結(jié)構(gòu)發(fā)生變化,JSON也不會導(dǎo)致反序列化失敗,因為可以通過字段名來匹配數(shù)據(jù)。
安全性更高:
- JSON格式天然安全,不會引發(fā)Java序列化中的反序列化漏洞。同時FastJson提供了對各種對象類型的安全處理機制,能過濾不安全的類型,進(jìn)一步保障系統(tǒng)安全。
FastJson使用示例:
import com.alibaba.fastjson.JSON;
public class FastJsonExample {
public static void main(String[] args) {
// 序列化
Person person = new Person("Alice", 25);
String jsonString = JSON.toJSONString(person);
System.out.println("序列化為JSON字符串:" + jsonString);
// 反序列化
Person deserializedPerson = JSON.parseObject(jsonString, Person.class);
System.out.println("反序列化對象:" + deserializedPerson.getName());
}
}
替代方案二:Protobuf
Protobuf(Protocol Buffers)是由Google推出的一種序列化框架,專門用于跨語言、跨平臺的高效數(shù)據(jù)序列化。
Protobuf的優(yōu)勢:
高效的二進(jìn)制格式:
- Protobuf會將對象數(shù)據(jù)序列化成緊湊的二進(jìn)制格式,占用的空間遠(yuǎn)小于JSON或Java原生序列化生成的數(shù)據(jù)。
- 序列化和反序列化速度極快,非常適合在網(wǎng)絡(luò)傳輸和高頻讀寫場景下使用。
跨語言:
- Protobuf支持多種編程語言(如Java、Python、C++等),可以在不同平臺之間無縫共享數(shù)據(jù)。這是Protobuf在分布式系統(tǒng)中被廣泛采用的重要原因。
支持嚴(yán)格的版本控制:
- Protobuf的“消息結(jié)構(gòu)”定義文件可以兼容新舊版本,當(dāng)數(shù)據(jù)結(jié)構(gòu)發(fā)生變化時,Protobuf可以自動適應(yīng)新舊字段,極大減少了版本兼容問題。
安全性強:
- Protobuf的格式是結(jié)構(gòu)化的,因此不易受到反序列化攻擊。數(shù)據(jù)結(jié)構(gòu)和解析過程非常明確,使用起來更安全。
Protobuf使用示例: Protobuf使用時,需要先定義一個.proto文件,指定數(shù)據(jù)結(jié)構(gòu):
// person.proto
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
}
然后,通過Protobuf編譯工具生成對應(yīng)的Java類,進(jìn)行序列化和反序列化:
Person person = Person.newBuilder().setName("Alice").setAge(25).build();
byte[] data = person.toByteArray(); // 序列化
Person deserializedPerson = Person.parseFrom(data); // 反序列化
在實際項目中,由于Java原生序列化的性能、安全和跨語言問題,開發(fā)者更傾向于選擇FastJson或Protobuf。FastJson適合數(shù)據(jù)格式簡單、語言可讀性要求高的應(yīng)用場景;Protobuf則適合大數(shù)據(jù)傳輸、分布式和跨平臺應(yīng)用中對效率和兼容性要求高的場景。兩者都比Java原生序列化更靈活、安全和高效。
4. 總結(jié)
序列化與反序列化是現(xiàn)代軟件開發(fā)中用于數(shù)據(jù)傳輸、存儲的基本技術(shù),尤其在分布式系統(tǒng)、緩存系統(tǒng)和跨語言數(shù)據(jù)交換中應(yīng)用廣泛。通過這些技術(shù),可以將復(fù)雜的對象在不同進(jìn)程、系統(tǒng)甚至平臺間傳遞和共享。
1. Java原生序列化的基礎(chǔ)
Java為對象序列化提供了內(nèi)置支持,使用Serializable接口和ObjectOutputStream/ObjectInputStream流實現(xiàn)簡單直接的序列化。Java原生序列化的流程包括:
- 將對象及其字段數(shù)據(jù)轉(zhuǎn)化為字節(jié)流,保存或傳輸。
- 在需要時通過字節(jié)流反序列化為對象,恢復(fù)對象狀態(tài)。
這種方式在同一Java系統(tǒng)內(nèi)部傳輸數(shù)據(jù)非常便捷,但存在性能、安全和兼容性等問題。
2. Java原生序列化的缺點
Java原生序列化適用于小規(guī)模數(shù)據(jù)傳輸或非跨平臺的Java系統(tǒng)。但在生產(chǎn)級別應(yīng)用中,它的缺陷較為明顯:
- 性能低:字節(jié)流數(shù)據(jù)體積大,序列化和反序列化速度相對較慢。
- 不跨語言:生成的字節(jié)流只能在Java環(huán)境中解析,無法直接用于與其他語言的交互。
- 安全性欠佳:存在反序列化漏洞風(fēng)險,容易受到反序列化攻擊。
因此,Java原生序列化適合臨時數(shù)據(jù)保存,但在涉及敏感數(shù)據(jù)和跨系統(tǒng)場景時,存在較大風(fēng)險。
3. 替代方案:FastJson和Protobuf
為了解決Java原生序列化的不足,許多開發(fā)者選擇更高效的序列化框架,例如FastJson和Protobuf:
FastJson:適合JSON格式的數(shù)據(jù)交換,提供跨平臺、通用的人類可讀格式。其序列化過程速度快,兼容性好,非常適合在前后端通信和跨平臺輕量級數(shù)據(jù)傳輸中使用。
Protobuf:適合分布式系統(tǒng)、大數(shù)據(jù)和高效跨平臺傳輸。Protobuf的二進(jìn)制格式占用空間小,解析速度快,版本控制友好,是數(shù)據(jù)高效傳輸?shù)睦硐脒x擇,尤其在微服務(wù)和多語言系統(tǒng)中應(yīng)用廣泛。
使用場景總結(jié)
- 選擇Java原生序列化:適合在同一Java應(yīng)用內(nèi)的數(shù)據(jù)存儲(如緩存、簡單對象的臨時保存),對性能和跨平臺要求不高的情況下。
- 選擇FastJson:適合需要將數(shù)據(jù)轉(zhuǎn)化為人類可讀格式且在前后端或不同系統(tǒng)中共享時,JSON格式兼容性強且更便于調(diào)試。
- 選擇Protobuf:適合大型、復(fù)雜的分布式系統(tǒng)和跨語言數(shù)據(jù)傳輸,對性能和傳輸效率要求高的情況。
綜上所述,序列化和反序列化技術(shù)的發(fā)展,為現(xiàn)代軟件提供了多樣化的數(shù)據(jù)傳輸和持久化方案。通過合理選擇合適的序列化工具,可以優(yōu)化系統(tǒng)的性能、安全性和兼容性,從而滿足不同應(yīng)用的需求。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot中實現(xiàn)JWT登錄授權(quán)+無感刷新全流程
在當(dāng)今的分布式系統(tǒng)和前后端分離架構(gòu)盛行的時代,傳統(tǒng)的基于?Session?的認(rèn)證方式逐漸暴露出諸多弊端,而?JWT的出現(xiàn),猶如一道曙光,為這些問題提供了完美的解決方案,本文將詳細(xì)地為大家講解?Spring?Boot?整合?JWT?實現(xiàn)登錄認(rèn)證與接口授權(quán)的全流程2026-04-04
idea設(shè)置在包里面在創(chuàng)建一個包方式
這篇文章主要介紹了idea設(shè)置在包里面在創(chuàng)建一個包方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
springboot+EHcache 實現(xiàn)文章瀏覽量的緩存和超時更新
這篇文章主要介紹了springboot+EHcache 實現(xiàn)文章瀏覽量的緩存和超時更新,問題描述和解決思路給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-04-04
Java?IO篇之Reactor?網(wǎng)絡(luò)模型的概念
Reactor?模式也叫做反應(yīng)器設(shè)計模式,是一種為處理服務(wù)請求并發(fā)提交到一個或者多個服務(wù)處理器的事件設(shè)計模式,Reactor?模式主要由?Reactor?和處理器?Handler?這兩個核心部分組成,本文給大家介紹Java?IO篇之Reactor?網(wǎng)絡(luò)模型的概念,感興趣的朋友一起看看吧2022-01-01
jstack報錯Unable to open socket file解決
這篇文章主要為大家介紹了jstack報錯Unable to open socket file的解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02
Java中json使用方法_動力節(jié)點Java學(xué)院整理
JSON(JavaScript Object Notation) 是一種輕量級的數(shù)據(jù)交換格式, json是個非常重要的數(shù)據(jù)結(jié)構(gòu),在web開發(fā)中應(yīng)用十分廣泛。下面通過本文給大家講解Java中json使用方法,感興趣的朋友一起看看吧2017-07-07

