詳解JAVA序列化及實際應(yīng)用場景分析
1.什么是序列化
序列化就是將對象轉(zhuǎn)換為可以存儲或傳輸?shù)男问剑詫崿F(xiàn)對象持久化存儲到磁盤中,或者在網(wǎng)絡(luò)中傳輸。要將對象轉(zhuǎn)為可以存儲或傳輸?shù)男问?,首先就要用一種統(tǒng)一的標準來描述對象,這樣序列化的對象才能被反序列化出來。不同的序列化算法就是用不同的標準來描述對象,序列化算法很多,這里舉幾個常見的:
- json
- xml
- yaml
- Java Serialization
前三種見名知意,就是分別用json、xml、yaml來描述對象,第四種Java Serialization是JDK默認的序列化算法,其使用了一種稱為 Java Object Serialization Stream Protocol 的二進制格式來描述JAVA對象。
2.JAVA中的序列化
JDK種提供了Serializable接口用來聲明哪些類可以被序列化,提供了ObjectOutputStream、ObjectOutputStream來進行序列化和反序列化。
JAVA的序列化中有幾個注意點:
- 成員變量必須可序列化
- transient關(guān)鍵字,可避免被序列化
- 無法更新狀態(tài)
- serialVersionUID
2.1.成員變量必須可序列化
如果所要序列化的對象的成員屬性中含有對其他對象的引用,要求所引用的對象也必須是可序列化的(實現(xiàn)serializable接口),否則會序列化失敗。
訂單對象中包含一個產(chǎn)品對象,Order實現(xiàn)了序列化接口,但是product沒有實現(xiàn)序列化接口:
序列化Order的時候會報錯:
2.2.transient關(guān)鍵字,可避免被序列化
用transient修飾屬性:
可以看到屬性值不會被序列化出去,其會是默認值:
2.3.無法更新狀態(tài)
由于java序利化算法不會重復序列化同一個對象,如果對象的內(nèi)容更改后,,再次序列化,并不會再次將此對象轉(zhuǎn)換為字節(jié)序列。
我們對同一個對象序列化兩次,然后輸出其屬性值:
可以看到其實只有第一次序列化是生效的:
2.4.serialVersionUID
序列化版本號,類似于樂觀鎖中的版本號,用來保證序列化后的字節(jié)序列沒有被改動過,反序列化回來后和原來的程序是兼容的。
serialVersionUID不會自動改變,而是留給程序員手動更改的一個版本號標志位。更改了序列化文件的程序員一并更改版本號提示后來的人文件被更改過。
如果在反序列化時,類的 serialVersionUID 與序列化時的版本號不匹配,那么會拋出 InvalidClassException
異常,表示類的版本不兼容,無法進行反序列化。
3.JDK序列化算法
Java Object Serialization Stream Protocol規(guī)定整個對象序列化后的文件由三部分組成:
- 頭部(Header):包含魔數(shù)(Magic Number)和版本號(Version Number)。魔數(shù)標識了該流是 Java 序列化流,版本號用于指定序列化協(xié)議的版本。
- 類描述符表(Class Descriptor Table):包含了序列化流中所引用的類的描述符信息。每個類描述符包括類的名稱、序列化編號、序列化版本號等信息。
- 對象數(shù)據(jù)(Object Data):按照序列化順序包含了被序列化對象的狀態(tài)信息。這包括了對象的實例變量、類信息等。
以上一節(jié)我們在D盤下生成了一個名叫Order.txt的序列化文件為例,我們來讀一讀JAVA的序列化文件。
要注意的是如果直接打開,因為編碼的原因看見的會是亂碼,需要用16進制的方式,打開它來看看,要注意的是普通的文本工具都沒辦法用16進制的方式直接打開文件,這里我們用代碼來將文本中的內(nèi)容以16進制的方式輸出,代碼如下:
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) { // 打印十六進制內(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來解析一下上面的字節(jié)內(nèi)容:
- 頭部(Header):
AC ED
表示 Java 序列化文件的標識符。 - 版本號:
00 05
表示版本號為 5。 - 對象數(shù)據(jù):
73 72 00 0E 63 6F 6D 2E 65 72 79 69 2E 4F 72 64 65 72
是一個類描述符,指明被序列化對象所屬的類為com.eryi.Order
。 - 對象數(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
是一個對象的實例數(shù)據(jù),包含了對象的狀態(tài)信息。 - 對象數(shù)據(jù):
4C 00 07 70 72 6F 64 75 63 74
是一個對象的實例變量的描述符,指明變量名為product
。 - 對象數(shù)據(jù):
74 00 12 4C 63 6F 6D 2F 65 72 79 69 2F 50 72 6F 64 75 63 74 3B
是一個字符串,表示變量值為"com.eryi.Product"
。 - 對象數(shù)據(jù):
78 70 74 00 03 34 35 36
是一個對象的實例變量的描述符,指明變量名為xpt
。 - 對象數(shù)據(jù):
73 72 00 10 63 6F 6D 2E 65 72 79 69 2E 50 72 6F 64 75 63 74
是一個類描述符,指明變量類型為com.eryi.Product
。 - 對象數(shù)據(jù):
15 0D 2C B6 A0 EE 95 4F 02 00 02
是一個對象的實例數(shù)據(jù),包含了變量的狀態(tài)信息。 - 對象數(shù)據(jù):
4C 00 04 6E 61 6D 65
是一個對象的實例變量的描述符,指明變量名為name
。 - 對象數(shù)據(jù):
71 00 7E 00 01
是一個字符串,表示變量值為"name"
。 - 對象數(shù)據(jù):
4C 00 05 70 72 69 63 65
是一個對象的實例變量的描述符,指明變量名為price
。 - 對象數(shù)據(jù):
71 00 7E 00 01
是一個字符串,表示變量值為"price"
。 - 對象數(shù)據(jù):
78 70 74 00 0C E6 B2 83 E5 B0 94 E6 B2 83 53 36 30
是一個對象的實例變量的描述符,指明變量名為xpt
。 - 對象數(shù)據(jù):
74 00 03 33 30 57
是一個字符串,表示變量值為"30W"
。
4.序列化在實際中的一些應(yīng)用
首先我們要知道序列化是可以跨JVM的,JDK的序列化算法只是規(guī)定了標準,所以可以在一個JVM上序列化,然后在另一個JVM中進行反序列化,這也就是說序列化可以用來進行通信時的數(shù)據(jù)傳輸。
一些自定義的將對象直接存為二進制或者其它進制的序列化協(xié)議(比如就上文的JDK序列化算法)在數(shù)據(jù)傳輸上具有很好的性能優(yōu)勢。因為直接將對象轉(zhuǎn)為了二進制(其它進制一樣的道理),接收端收到數(shù)據(jù)后直接就可以通過二進制數(shù)據(jù)流反序列化得到對象。如果是以JSON之類的文本結(jié)構(gòu)傳輸數(shù)據(jù),那么接收端收到數(shù)據(jù)后要首先將二進制數(shù)據(jù)流轉(zhuǎn)為文本結(jié)構(gòu),然后再解析文本結(jié)構(gòu)將其轉(zhuǎn)為對象,整個過程就要慢很多。
由于上面的原因,在很多追求高性能的通信場景,經(jīng)常會自定義序列化協(xié)議。比如dubbo,dubbo作為以高性能著稱的RPC框架,其高性能有一方面就體現(xiàn)在使用了序列化上。dubbo自定義了報文和序列化協(xié)議,然后通過序列化的方式將數(shù)據(jù)直接塞進自定義的報文結(jié)構(gòu)中,接收端收到后直接反序列化就可以得到數(shù)據(jù)。
同時,序列化又存在安全隱患,由于serialVersionUID和數(shù)據(jù)沒有任何關(guān)系,修改屬性的數(shù)據(jù)值后,仍然可以反序列化回來,而且任何JVM拿到序列化的數(shù)據(jù)都可以進行反序列化,會存在數(shù)據(jù)被攔截然后惡意修改的風險。不過這個問題并不是序列化所獨有的問題,只要是沒有加密機制的通信協(xié)議都會存在這個問題,相比于同樣是透明傳輸?shù)腍TTP來說,用序列化在JAVA EE體系中傳數(shù)據(jù)對象確實性能更優(yōu)。
到此這篇關(guān)于詳解JAVA序列化的文章就介紹到這了,更多相關(guān)java序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java文件操作代碼片斷實例實現(xiàn)統(tǒng)計文件中字母出現(xiàn)的個數(shù)功能
本文介紹java讀文件實例,實現(xiàn)統(tǒng)計某一目錄下每個文件中出現(xiàn)的字母個數(shù)、數(shù)字個數(shù)、空格個數(shù)及行數(shù),除此之外沒有其他字符,大家參考使用吧2014-01-01Spring Boot結(jié)合IDEA自帶Maven插件如何快速切換profile
IDEA是目前 Java 開發(fā)者中使用最多的開發(fā)工具,它有著簡約的設(shè)計風格,強大的集成工具,便利的快捷鍵,這篇文章主要介紹了Spring Boot結(jié)合IDEA自帶Maven插件快速切換profile,需要的朋友可以參考下2023-03-03基于springboot的flowable工作流實戰(zhàn)流程分析
這篇文章主要介紹了基于springboot的flowable工作流實戰(zhàn)流程分析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-10-10詳解Spring Boot的GenericApplicationContext使用教程
這篇教程展示了如何在Spring應(yīng)用程序中使用GenericApplicationContext 。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11FeignClient如何通過配置變量調(diào)用配置文件url
這篇文章主要介紹了FeignClient如何通過配置變量調(diào)用配置文件url,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06