詳細(xì)說一說Java序列化的幾種方式對比
what
什么是序列化
首先要明白,序列化它是一個過程,什么過程呢?
把一個java對象轉(zhuǎn)化成字節(jié)序列的過程
java對象都知道,那什么是字節(jié)序列呢?
字節(jié),也就是byte,1byte = 8bit,也就是一個字節(jié)等于8位,每一位都是用0或者1來表示,在內(nèi)存中,數(shù)據(jù)就是以二進制的形式存儲的
那序列呢?簡單看來說就好比排隊,一列一列的,至此,字節(jié)序列,是不是就是像字節(jié)在排隊一樣,而字節(jié)又是一個個的8bit,理解了吧!
所以,Java中的序列化就是把Java對象變成二進制內(nèi)容的一個過程,也就是從內(nèi)存中把數(shù)據(jù)存儲下來,而數(shù)據(jù)在內(nèi)存中都是二進制的形式!
記住了,Java序列化出來的東西就是一個二進制內(nèi)容,就是對象在內(nèi)存中的存儲形式!
變量存儲角度理解序列化
接下來再以變量在內(nèi)存中的存儲為例去理解什么是序列化!
舉一個例子,比如寫一個變量:
String name = "張三";
也就是剛開始,把name的值設(shè)置成為”張三“,在后面的程序當(dāng)中,可以修改這個name,比如又把name的值修改成為”李四“!
程序的運行,需要把數(shù)據(jù)加載進內(nèi)存才可以,也就是說,無論是最開始的”張三“還是后來的”李四“都會被加載進內(nèi)存運行,那內(nèi)存都會為這些變量分配內(nèi)存!
可是,一旦程序執(zhí)行完畢,變量所分配或者說所占用的內(nèi)存就會被回收, 程序也就結(jié)束了,不過在這個過程中,可以從內(nèi)存中把這些變量存儲下來,那這個過程就叫做序列化!
反序列化
當(dāng)理解了什么是序列化之后,那反序列化也就不是問題了,自然而然的就懂了,所謂的反序列化用稍微專業(yè)點的話說就是:
把字節(jié)序列還原成對象的過程
由此可見,無論序列化還是反序列化,都是對象和字節(jié)序列之間的互相轉(zhuǎn)換!
序列化的多樣性
以上得知,序列化是一個對象和字節(jié)序列互相轉(zhuǎn)換的過程,那隨之而來的一個問題就是,該怎么轉(zhuǎn)換?
也就是該如何實現(xiàn)把對象序列化成字節(jié)序列,然后再把字節(jié)序列反序列化成對象,這其中必然存在一種規(guī)則,序列化和反序列化都必須按照這個規(guī)則來!
那這個規(guī)則就是序列化協(xié)議,那由此基本可以得出,可能存在不同的序列化協(xié)議,然后有不同的方式去實現(xiàn)序列化。
也就是不管怎么處理,最終實現(xiàn)的目的是對象和字節(jié)序列的互相轉(zhuǎn)換即可,比如Java就有其自己實現(xiàn)的一套序列化機制,可以把Java對象序列化成字節(jié)序列,還可以把自己序列再通過反序列化還原成原來的對象!
除了Java,像熟知的json也有其自己的序列化技術(shù),加入用Java的序列化技術(shù)把一個對象序列化成了字節(jié)序列,那用json的反序列化技術(shù)是無法將其還原成原本的Java對象的!
how
Java序列化demo演示
比如定義一個簡單的Person類,包含以下簡單屬性:
private String name; private int age;
接著就可以將其序列化,具體操作如下:
FileOutputStream outputStream = new FileOutputStream("C:\\Users\\ithuangqing\\desktop\\person.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
Person person = new Person();
person.setName("張三");
person.setAge(25);
objectOutputStream.writeObject(person);
objectOutputStream.flush();
objectOutputStream.close();以上就是在Java中的序列化過程,執(zhí)行該程序 ,會在桌面生成一個person.txt的文本文件,查看下內(nèi)容,發(fā)現(xiàn)是亂碼:

可以使用專業(yè)的十六進制編輯器查看,可以看到相關(guān)的十六進制內(nèi)容以及二進制內(nèi)容,這個就是Java對象序列化后的字節(jié)序列了。
接著看看如何將其反序列化,也就是將其還原成原來的 Java 對象!
//反序列化
Person person = null;
File file;
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\ithuangqing\\desktop\\person.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
person = (Person) objectInputStream.readObject();
objectInputStream.close();
fileInputStream.close();
System.out.println(person.getName());可以得到相應(yīng)的結(jié)果,執(zhí)行上述反序列化程序,可以得出:

正確得到之前設(shè)置的name,說明反序列化成功,以上就是在Java中的序列化和反序列化操作!
查看序列化出來的內(nèi)容
可以查看序列化出來的字節(jié)序,會發(fā)現(xiàn)以txt文本形式查看是一些亂碼,亂七八糟的,也看不懂,可以以二進制內(nèi)容查看!

這些二進制內(nèi)容,就是該Peroson對象在內(nèi)存中的存儲形式!這也是Java把對象序列化出來的二進制內(nèi)容!
序列化條件Serializable
一個Java對象要想實現(xiàn)序列化的功能,那就必須實現(xiàn)一個接口,如下:

可以查看該接口的定義:

可以發(fā)現(xiàn),這就是一個空接口,在Java中這樣的接口叫做“標(biāo)記接口”英文叫做Marker Interface,一般來說,一個類如果實現(xiàn)了一個標(biāo)記接口,起到的作用僅僅是給自己增加一個標(biāo)簽,比如上述這個,如果一個類實現(xiàn)了這個接口,那就會給自己身增加一個“序列化”的標(biāo)簽,說明這個類可以實現(xiàn)序列化相關(guān)功能!
為什么要實現(xiàn)Serializable接口
那為什么要實現(xiàn)Serializable接口呢難道就是為了讓其實現(xiàn)序列化?說起來好像是這么回事,但是還是有一些深入的內(nèi)容需要了解的!
首先來看兩個類:
1、ObjectOutputStream
2、ObjectInputStream
兩個類:經(jīng)過之前的代碼演示清楚了,對于Java的序列化而言就是通過上述兩個數(shù)據(jù)流類來完成的,也就是說他們包含了序列化和反序列化對象的方法,分別如下:


先拿ObjectOutputStream來說,這個類包含很多寫方法來寫各種的數(shù)據(jù)類型,舉一個例子來看:

可以看到其中包含一個writeInt方法可以將基本數(shù)據(jù)類型寫到外部文件中,同樣的則可以使用ObjectInputStream 再將外部的數(shù)據(jù)讀取進來:

查看打印輸出:

所以對于 ObjectInputStream 和 ObjectOutputStream這種高層次的數(shù)據(jù)流來說,它可以完成這樣的一些操作,就是可以通過寫操作將數(shù)據(jù)類型轉(zhuǎn)換為字節(jié)流,而讀操作又可以將字節(jié)流轉(zhuǎn)換成特定的數(shù)據(jù)類型!
那對于Java這種高級面向?qū)ο缶幊陶Z言而言,對象則是Java中所有數(shù)據(jù)的一個類型載體,因此也要可以對對象進行相應(yīng)的讀寫,說白了就是需要讓Java虛擬機知道在進行IO操作的時候,如何進行對象和字節(jié)流之間的轉(zhuǎn)換,而Serializable接口就起到了這樣的作用!
所以對于ObjectInputStream 和 ObjectOutputStream來說最主要的作用是進行Java序列化的操作,主要是因為他們提供了如下的兩個方法:
1、序列化一個對象
public final void writeObject(Object x) throws IOException
2、反序列化對象
public final Object readObject() throws IOException, ClassNotFoundException
.ser文件
可以看這里的一步操作:

也就是把Java對象序列化出來的字節(jié)序列內(nèi)容存儲到了一個txt文本文件里面,不過,就好比寫的與i寫純文本文件的格式txt,寫的Java文件格式是java,也就是后綴名是.java,所以這里序列化出來的文件也有一個標(biāo)準(zhǔn)的格式叫做.ser
那為什么叫做這個呢?因為Java中要把一個對象序列化需要實現(xiàn)Serializable,看這個單詞的開頭,操作一下:
執(zhí)行該序列化程序,會得到該文件:

可以打開看下:

以二進制內(nèi)容查看:

和之前的txt的內(nèi)容是一致的!說這個只是希望大家后續(xù)在見到.ser文件知道這是一個序列化文件!
再看序列化是什么
到了這里再來看看,什么是Java的序列化:
把Java對象在內(nèi)存中的狀態(tài)給存儲下來的一個過程,會得到一個該Java對象的字節(jié)序列,可以說是一個二進制內(nèi)容,本質(zhì)上是一個byte[]數(shù)組!
為什么說是byte[]數(shù)組呢?1byte = 8bit,也就是8位一組,也就是一個字節(jié),而二進制內(nèi)存都是0和1這種8位8位的,看下:

看著這張圖,可以理解為什么是byte[]數(shù)組了吧!
why
序列化可以用在哪些地方
經(jīng)過之前的描述知道了,通過序列化,可以把Java對象轉(zhuǎn)換成字節(jié)序列,也就是二進制內(nèi)容,這個內(nèi)容中就包含了對象的相關(guān)數(shù)據(jù),以及對象的類型信息和存儲在對象中的數(shù)據(jù)的類型!
也就是說,通過反序列化是可以還原這個Java對象的,而且這個過程是Java虛擬機來操作的,是直接由Java虛擬機來構(gòu)建這個對象,所以這個對象中的構(gòu)造函數(shù)是不會得到執(zhí)行的,因為根本不會經(jīng)過這一步,虛擬機直接把對象給整出來了!
那就知道了,反序列化是由虛擬機來搞定的,那么也就是實現(xiàn)了,在一個平臺上序列化出來的對象,完全可以放到另一個平臺上去反序列化該對象!
因此,這就造成了,序列化可以用于一些特定的數(shù)據(jù)傳輸:
1、把內(nèi)存中的對象保存起來,比如保存到數(shù)據(jù)庫中或者保存到一個文件中,以便長期保存
2、使用socket進行網(wǎng)絡(luò)數(shù)據(jù)傳輸
3、RMI(即遠(yuǎn)程調(diào)用Remote Method Invocation)的使用,也就是要利用對象序列化去運行遠(yuǎn)程主機上的服務(wù),以達到就像在本地機上運行對象時一樣。
序列化有哪些好處
那序列化有什么好處呢?或者說為什么要用序列化呢?
首先,數(shù)據(jù)其實是比較復(fù)雜的,比如對象啊,文件啊,等等都有各自不同的數(shù)據(jù)格式,怎么能把它們統(tǒng)一保存呢?這不,序列化就可以啊,經(jīng)過序列化之后,別管是什么,都保存在一塊了,都是字節(jié)序列,這就方便數(shù)據(jù)之間的傳輸了,因為格式統(tǒng)一!
另外,序列化保存的是對象在內(nèi)存中的保存狀態(tài),反序列化的時候是由Java虛擬機直接將該類還原在內(nèi)存中,簡潔快速而高效!
還有就是有的時候需要把Java對象從內(nèi)存中保存下來,以便脫離內(nèi)存,存儲在磁盤上,達到長期保存的目的,那這個序列化就很合適了!
序列化注意事項
異常
這里主要有兩個異常:
1、ClassNotFoundException:沒有找到對應(yīng)的Class
2、InvalidClassException:Class不匹配
首先說一下第一個異常,也就是ClassNotFoundException,這個其實是比較簡單的,也就是類沒找到,什么類沒找到呢?代碼復(fù)現(xiàn)一下:

執(zhí)行該代碼,就會發(fā)生問題:

這個錯誤其實已經(jīng)說的很明顯了,沒有找到Person這個類了,那是因為把之前的Person改成Person1了,所以反序列化就找不到對應(yīng)的類,這就是無法實現(xiàn)反序列化的:

再把Person1改回Person試一下:

也就是反序列化的時候,得有個對應(yīng)的類,而且這個類的路徑位置啥的也是不能改變的:

接下來看下InvalidClassException這個也就是Class不匹配的問題。
同樣,通過代碼來演示:

為什么會出現(xiàn)這個錯誤呢?看這里:

之前是int,反序列化的時候這成了long,不就是不匹配嘛,所以這樣也是會出錯的!
所以啊,總的來說,就是,序列化的時候是什么樣子的,反序列化個一模一樣的,但是如果就沒有定義這樣的一個類,那反序列化肯定就出錯了!
如何解決:serialVersionUID
為了解決InvalidClassException的問題,就出現(xiàn)了這么一個東西,先看代碼:

這個操作大家應(yīng)該不陌生吧,應(yīng)該都見過的,它的出現(xiàn)主要就是為了解決Class類型不匹配的問題,它作為標(biāo)識Java類的序列化版本,可以看作是一個標(biāo)記,一般來說,可以由IDE自動生成,如果修改了類中的字段什么的,那這個serialVersionUID就會發(fā)生改變,通過這樣的一種機制來自動阻止不匹配的class版本!
在之前進行序列化操作的時候并沒有創(chuàng)建這個serialVersionUID,但是,沒有顯式創(chuàng)建就并不代表它沒有,而是會生成默認(rèn)的serialVersionUID,但是,在實際當(dāng)中,Java官方建議還是要顯式的創(chuàng)建serialVersionUID!
為啥,因為如果不顯式創(chuàng)建,那么默認(rèn)的生成時高度依賴于JVM的,但是如果序列化和反序列化是跨平臺操作的化,就有可能會發(fā)生前后創(chuàng)建的serialVersionUID不一致從而導(dǎo)致反序列化出現(xiàn)異常,所以還是要顯式創(chuàng)建serialVersionUID,保證即使跨平臺這個serialVersionUID也是一致的!
那serialVersionUID是如何起作用的呢?
說的簡單點就是,序列化的時候這個serialVersionUID也被序列化進去了,那在反序列化的時候,JVM就會把傳進來的字節(jié)流中的serialVersionUID與本地對應(yīng)的類中的serialVersionUID進行比較,看是否一致,一致就可以反序列化,不一致就不能反序列化,看代碼演示:

這里顯式創(chuàng)建serialVersionUID,然后進行序列化操作生成新的字節(jié)序列內(nèi)容,在反序列化之前,把這個serialVersionUID給修改下:

然后進行反序列化操作:

產(chǎn)生異常了,看來是類不匹配,看下這個異常描述:

說的是不是很清楚了!
那這個時候,再次將起修改成1L,然后做如下操作:

那根據(jù)之前說的,這里修改了類,那反序列化的化就會出現(xiàn)InvalidClassException 問題,那執(zhí)行反序列化看下:

果然報錯,類型問題,不過再來看一個情況:

再反序列化試下:

正常輸出,沒有報錯,但是如果沒有顯式自定義serialVersionUID的話,那就會由系統(tǒng)自定義生成,那就會報錯了!
所以一定要注意serialVersionUID的加與不加的一些區(qū)別和可能會產(chǎn)生的問題!也就是說序列化完成之后,如果原類型字段增加或者減少,不指定serialVersionUID的情況下,也是會報不一致的錯誤。指定了則不報錯!
Java的序列化安全嘛
以上較為詳細(xì)的介紹了Java的序列化操作,那么現(xiàn)在來思考這樣的一個問題,Java的序列化安全嘛?也就是使用Java序列化的操作會不會產(chǎn)生什么安全隱患?
想象一下這個,就是,序列化生成的字節(jié)序列可以通過反序列化直接將其在內(nèi)存中還原成原狀態(tài),那如果某個字節(jié)序列是特意設(shè)置好的,含有一些不安全代碼,那直接給還原到內(nèi)存中了,是不是會產(chǎn)生一些安全問題!
所以Java中提供的序列化機制,本身是存在一些安全性問題的,那更好的辦法是啥呢?可以通過使用json來實現(xiàn),這樣輸出的數(shù)據(jù)都是一些基本類型的內(nèi)容,不像Java序列化那樣,序列化輸出的包含了很多對象相關(guān)信息!
總結(jié)
到此這篇關(guān)于Java序列化幾種方式對比的文章就介紹到這了,更多相關(guān)Java序列化幾種方式對比內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java POI 如何實現(xiàn)Excel單元格內(nèi)容換行
這篇文章主要介紹了java POI 如何實現(xiàn)Excel單元格內(nèi)容換行的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
IDEA創(chuàng)建的maven項目中pom.xml增加新依賴無效問題及解決
在IDEA中,解決maven項目pom.xml增加依賴但外部庫未更新的問題,可以通過設(shè)置"構(gòu)建腳本更改后同步項目"選項為"任何更改",然后刷新Maven項目來解決2025-01-01
java實現(xiàn)給出分?jǐn)?shù)數(shù)組得到對應(yīng)名次數(shù)組的方法
這篇文章主要介紹了java實現(xiàn)給出分?jǐn)?shù)數(shù)組得到對應(yīng)名次數(shù)組的方法,涉及java針對數(shù)組的遍歷、排序及運算的相關(guān)技巧,需要的朋友可以參考下2015-07-07

