詳解JAVA序列化及實(shí)際應(yīng)用場(chǎng)景分析
1.什么是序列化
序列化就是將對(duì)象轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问?,以?shí)現(xiàn)對(duì)象持久化存儲(chǔ)到磁盤中,或者在網(wǎng)絡(luò)中傳輸。要將對(duì)象轉(zhuǎn)為可以存儲(chǔ)或傳輸?shù)男问?,首先就要用一種統(tǒng)一的標(biāo)準(zhǔn)來(lái)描述對(duì)象,這樣序列化的對(duì)象才能被反序列化出來(lái)。不同的序列化算法就是用不同的標(biāo)準(zhǔn)來(lái)描述對(duì)象,序列化算法很多,這里舉幾個(gè)常見(jiàn)的:
- json
- xml
- yaml
- Java Serialization
前三種見(jiàn)名知意,就是分別用json、xml、yaml來(lái)描述對(duì)象,第四種Java Serialization是JDK默認(rèn)的序列化算法,其使用了一種稱為 Java Object Serialization Stream Protocol 的二進(jìn)制格式來(lái)描述JAVA對(duì)象。
2.JAVA中的序列化
JDK種提供了Serializable接口用來(lái)聲明哪些類可以被序列化,提供了ObjectOutputStream、ObjectOutputStream來(lái)進(jìn)行序列化和反序列化。
JAVA的序列化中有幾個(gè)注意點(diǎn):
- 成員變量必須可序列化
- transient關(guān)鍵字,可避免被序列化
- 無(wú)法更新?tīng)顟B(tài)
- serialVersionUID
2.1.成員變量必須可序列化
如果所要序列化的對(duì)象的成員屬性中含有對(duì)其他對(duì)象的引用,要求所引用的對(duì)象也必須是可序列化的(實(shí)現(xiàn)serializable接口),否則會(huì)序列化失敗。
訂單對(duì)象中包含一個(gè)產(chǎn)品對(duì)象,Order實(shí)現(xiàn)了序列化接口,但是product沒(méi)有實(shí)現(xiàn)序列化接口:

序列化Order的時(shí)候會(huì)報(bào)錯(cuò):

2.2.transient關(guān)鍵字,可避免被序列化
用transient修飾屬性:

可以看到屬性值不會(huì)被序列化出去,其會(huì)是默認(rèn)值:

2.3.無(wú)法更新?tīng)顟B(tài)
由于java序利化算法不會(huì)重復(fù)序列化同一個(gè)對(duì)象,如果對(duì)象的內(nèi)容更改后,,再次序列化,并不會(huì)再次將此對(duì)象轉(zhuǎn)換為字節(jié)序列。
我們對(duì)同一個(gè)對(duì)象序列化兩次,然后輸出其屬性值:

可以看到其實(shí)只有第一次序列化是生效的:

2.4.serialVersionUID
序列化版本號(hào),類似于樂(lè)觀鎖中的版本號(hào),用來(lái)保證序列化后的字節(jié)序列沒(méi)有被改動(dòng)過(guò),反序列化回來(lái)后和原來(lái)的程序是兼容的。
serialVersionUID不會(huì)自動(dòng)改變,而是留給程序員手動(dòng)更改的一個(gè)版本號(hào)標(biāo)志位。更改了序列化文件的程序員一并更改版本號(hào)提示后來(lái)的人文件被更改過(guò)。
如果在反序列化時(shí),類的 serialVersionUID 與序列化時(shí)的版本號(hào)不匹配,那么會(huì)拋出 InvalidClassException 異常,表示類的版本不兼容,無(wú)法進(jìn)行反序列化。
3.JDK序列化算法
Java Object Serialization Stream Protocol規(guī)定整個(gè)對(duì)象序列化后的文件由三部分組成:
- 頭部(Header):包含魔數(shù)(Magic Number)和版本號(hào)(Version Number)。魔數(shù)標(biāo)識(shí)了該流是 Java 序列化流,版本號(hào)用于指定序列化協(xié)議的版本。
- 類描述符表(Class Descriptor Table):包含了序列化流中所引用的類的描述符信息。每個(gè)類描述符包括類的名稱、序列化編號(hào)、序列化版本號(hào)等信息。
- 對(duì)象數(shù)據(jù)(Object Data):按照序列化順序包含了被序列化對(duì)象的狀態(tài)信息。這包括了對(duì)象的實(shí)例變量、類信息等。
以上一節(jié)我們?cè)贒盤下生成了一個(gè)名叫Order.txt的序列化文件為例,我們來(lái)讀一讀JAVA的序列化文件。
要注意的是如果直接打開(kāi),因?yàn)榫幋a的原因看見(jiàn)的會(huì)是亂碼,需要用16進(jìn)制的方式,打開(kāi)它來(lái)看看,要注意的是普通的文本工具都沒(méi)辦法用16進(jìn)制的方式直接打開(kāi)文件,這里我們用代碼來(lái)將文本中的內(nèi)容以16進(jìn)制的方式輸出,代碼如下:
public static void main(String[] args) throws IOException {
displayFileInHex("D:\\Order.txt");
}
private static void displayFileInHex(String filePath) throws IOException {
try (FileInputStream fileIn = new FileInputStream(filePath)) {
int bytesRead;
byte[] buffer = new byte[16];
while ((bytesRead = fileIn.read(buffer)) != -1) {
// 打印十六進(jìn)制內(nèi)容
for (int i = 0; i < bytesRead; i++) {
System.out.printf("%02X ", buffer[i]);
}
// 填充缺失的位置
if (bytesRead < 16) {
int missingBytes = 16 - bytesRead;
for (int i = 0; i < missingBytes; i++) {
System.out.print(" ");
}
}
System.out.println("\n");
}
}
}輸出結(jié)果:
用Java Object Serialization Stream Protocol來(lái)解析一下上面的字節(jié)內(nèi)容:
- 頭部(Header):
AC ED表示 Java 序列化文件的標(biāo)識(shí)符。 - 版本號(hào):
00 05表示版本號(hào)為 5。 - 對(duì)象數(shù)據(jù):
73 72 00 0E 63 6F 6D 2E 65 72 79 69 2E 4F 72 64 65 72是一個(gè)類描述符,指明被序列化對(duì)象所屬的類為com.eryi.Order。 - 對(duì)象數(shù)據(jù):
9D F0 BD D3 7C 8B DA 85 02 00 02 4C 00 0B 6F 72 64 65 72 4E 75 6D 62 65 72 74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B是一個(gè)對(duì)象的實(shí)例數(shù)據(jù),包含了對(duì)象的狀態(tài)信息。 - 對(duì)象數(shù)據(jù):
4C 00 07 70 72 6F 64 75 63 74是一個(gè)對(duì)象的實(shí)例變量的描述符,指明變量名為product。 - 對(duì)象數(shù)據(jù):
74 00 12 4C 63 6F 6D 2F 65 72 79 69 2F 50 72 6F 64 75 63 74 3B是一個(gè)字符串,表示變量值為"com.eryi.Product"。 - 對(duì)象數(shù)據(jù):
78 70 74 00 03 34 35 36是一個(gè)對(duì)象的實(shí)例變量的描述符,指明變量名為xpt。 - 對(duì)象數(shù)據(jù):
73 72 00 10 63 6F 6D 2E 65 72 79 69 2E 50 72 6F 64 75 63 74是一個(gè)類描述符,指明變量類型為com.eryi.Product。 - 對(duì)象數(shù)據(jù):
15 0D 2C B6 A0 EE 95 4F 02 00 02是一個(gè)對(duì)象的實(shí)例數(shù)據(jù),包含了變量的狀態(tài)信息。 - 對(duì)象數(shù)據(jù):
4C 00 04 6E 61 6D 65是一個(gè)對(duì)象的實(shí)例變量的描述符,指明變量名為name。 - 對(duì)象數(shù)據(jù):
71 00 7E 00 01是一個(gè)字符串,表示變量值為"name"。 - 對(duì)象數(shù)據(jù):
4C 00 05 70 72 69 63 65是一個(gè)對(duì)象的實(shí)例變量的描述符,指明變量名為price。 - 對(duì)象數(shù)據(jù):
71 00 7E 00 01是一個(gè)字符串,表示變量值為"price"。 - 對(duì)象數(shù)據(jù):
78 70 74 00 0C E6 B2 83 E5 B0 94 E6 B2 83 53 36 30是一個(gè)對(duì)象的實(shí)例變量的描述符,指明變量名為xpt。 - 對(duì)象數(shù)據(jù):
74 00 03 33 30 57是一個(gè)字符串,表示變量值為"30W"。
4.序列化在實(shí)際中的一些應(yīng)用
首先我們要知道序列化是可以跨JVM的,JDK的序列化算法只是規(guī)定了標(biāo)準(zhǔn),所以可以在一個(gè)JVM上序列化,然后在另一個(gè)JVM中進(jìn)行反序列化,這也就是說(shuō)序列化可以用來(lái)進(jìn)行通信時(shí)的數(shù)據(jù)傳輸。
一些自定義的將對(duì)象直接存為二進(jìn)制或者其它進(jìn)制的序列化協(xié)議(比如就上文的JDK序列化算法)在數(shù)據(jù)傳輸上具有很好的性能優(yōu)勢(shì)。因?yàn)橹苯訉?duì)象轉(zhuǎn)為了二進(jìn)制(其它進(jìn)制一樣的道理),接收端收到數(shù)據(jù)后直接就可以通過(guò)二進(jìn)制數(shù)據(jù)流反序列化得到對(duì)象。如果是以JSON之類的文本結(jié)構(gòu)傳輸數(shù)據(jù),那么接收端收到數(shù)據(jù)后要首先將二進(jìn)制數(shù)據(jù)流轉(zhuǎn)為文本結(jié)構(gòu),然后再解析文本結(jié)構(gòu)將其轉(zhuǎn)為對(duì)象,整個(gè)過(guò)程就要慢很多。
由于上面的原因,在很多追求高性能的通信場(chǎng)景,經(jīng)常會(huì)自定義序列化協(xié)議。比如dubbo,dubbo作為以高性能著稱的RPC框架,其高性能有一方面就體現(xiàn)在使用了序列化上。dubbo自定義了報(bào)文和序列化協(xié)議,然后通過(guò)序列化的方式將數(shù)據(jù)直接塞進(jìn)自定義的報(bào)文結(jié)構(gòu)中,接收端收到后直接反序列化就可以得到數(shù)據(jù)。
同時(shí),序列化又存在安全隱患,由于serialVersionUID和數(shù)據(jù)沒(méi)有任何關(guān)系,修改屬性的數(shù)據(jù)值后,仍然可以反序列化回來(lái),而且任何JVM拿到序列化的數(shù)據(jù)都可以進(jìn)行反序列化,會(huì)存在數(shù)據(jù)被攔截然后惡意修改的風(fēng)險(xiǎn)。不過(guò)這個(gè)問(wèn)題并不是序列化所獨(dú)有的問(wèn)題,只要是沒(méi)有加密機(jī)制的通信協(xié)議都會(huì)存在這個(gè)問(wèn)題,相比于同樣是透明傳輸?shù)腍TTP來(lái)說(shuō),用序列化在JAVA EE體系中傳數(shù)據(jù)對(duì)象確實(shí)性能更優(yōu)。
到此這篇關(guān)于詳解JAVA序列化的文章就介紹到這了,更多相關(guān)java序列化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java文件操作代碼片斷實(shí)例實(shí)現(xiàn)統(tǒng)計(jì)文件中字母出現(xiàn)的個(gè)數(shù)功能
本文介紹java讀文件實(shí)例,實(shí)現(xiàn)統(tǒng)計(jì)某一目錄下每個(gè)文件中出現(xiàn)的字母?jìng)€(gè)數(shù)、數(shù)字個(gè)數(shù)、空格個(gè)數(shù)及行數(shù),除此之外沒(méi)有其他字符,大家參考使用吧2014-01-01
Java中JSON字符串反序列化(動(dòng)態(tài)泛型)
文章討論了在定時(shí)任務(wù)中使用反射調(diào)用目標(biāo)對(duì)象時(shí)處理動(dòng)態(tài)參數(shù)的問(wèn)題,通過(guò)將方法參數(shù)存儲(chǔ)為JSON字符串并進(jìn)行反序列化,可以實(shí)現(xiàn)動(dòng)態(tài)調(diào)用,然而,這種方式容易導(dǎo)致內(nèi)存溢出(OOM),這篇文章主要介紹了JSON字符串反序列化?動(dòng)態(tài)泛型,需要的朋友可以參考下2024-12-12
Spring Boot結(jié)合IDEA自帶Maven插件如何快速切換profile
IDEA是目前 Java 開(kāi)發(fā)者中使用最多的開(kāi)發(fā)工具,它有著簡(jiǎn)約的設(shè)計(jì)風(fēng)格,強(qiáng)大的集成工具,便利的快捷鍵,這篇文章主要介紹了Spring Boot結(jié)合IDEA自帶Maven插件快速切換profile,需要的朋友可以參考下2023-03-03
基于springboot的flowable工作流實(shí)戰(zhàn)流程分析
這篇文章主要介紹了基于springboot的flowable工作流實(shí)戰(zhàn)流程分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-10-10
詳解Spring Boot的GenericApplicationContext使用教程
這篇教程展示了如何在Spring應(yīng)用程序中使用GenericApplicationContext 。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11
Java中l(wèi)ambda表達(dá)式的基本運(yùn)用
大家好,本篇文章主要講的是Java中l(wèi)ambda表達(dá)式的基本運(yùn)用,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
FeignClient如何通過(guò)配置變量調(diào)用配置文件url
這篇文章主要介紹了FeignClient如何通過(guò)配置變量調(diào)用配置文件url,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06


