Java 序列化與反序列化的分析

公眾號(hào):追風(fēng)棧Binary

我們通過(guò)文本或者數(shù)據(jù)庫(kù)的方式來(lái)永久化存儲(chǔ)程序中產(chǎn)生的數(shù)據(jù)凭迹。這種思路也可以借鑒到保存Java對(duì)象這類問(wèn)題上來(lái),因?yàn)镴ava對(duì)象的存在是臨時(shí)性的官疲,等到程序結(jié)束后就會(huì)被垃圾回收機(jī)制進(jìn)行回收。所以需要特定的手段來(lái)完成相應(yīng)的轉(zhuǎn)換過(guò)程。

什么是Java對(duì)象的序列化和反序列化摧冀?

以一個(gè)POJO(Plain Ordinary Java Object,簡(jiǎn)單的Java對(duì)象)User類為例,當(dāng)我們創(chuàng)建了一個(gè)User對(duì)象myUser之后系宫,這個(gè)myUser對(duì)象就會(huì)存儲(chǔ)在JVM(Java虛擬機(jī))上的堆內(nèi)存中索昂,只要垃圾回收機(jī)制沒(méi)有盯上myUser,那么在程序運(yùn)行期間我們總能有方法來(lái)訪問(wèn)到這個(gè)對(duì)象所包含的屬性以及方法扩借。而當(dāng)JVM停止運(yùn)行椒惨,那么myUser就會(huì)被回收,我們就再也拿不到myUser所包含的屬性和方法了往枷。

那問(wèn)題是,如果我們需要保存這個(gè)myUser對(duì)象的狀態(tài)信息凄杯,并加以保存错洁,并在以后有需要的情況下將它原封不動(dòng)的加載回來(lái)。那這個(gè)過(guò)程戒突,就是Java對(duì)象的序列化與反序列化屯碴。我自己將這個(gè)過(guò)程畫(huà)了個(gè)圖,如下:

序列化與反序列化

從術(shù)語(yǔ)角度來(lái)說(shuō)膊存,Serialization是Java內(nèi)置的一個(gè)API导而,通過(guò)這個(gè)步驟,將對(duì)象狀態(tài)信息轉(zhuǎn)化為字節(jié)流的方式隔崎,用于存儲(chǔ)或者作為信息進(jìn)行傳輸今艺;而后再根據(jù)需要,通過(guò)反序列化的方式爵卒,將字節(jié)流轉(zhuǎn)換成初始的對(duì)象虚缎。例如上圖中類生成的對(duì)象小明,我們可以把他變成一種文本方式存儲(chǔ)起來(lái)钓株,等需要小明工作的時(shí)候实牡,再將小明復(fù)原陌僵。這就是序列化和反序列的基本原理。

什么是Serializable创坞?

SerializableJava io包中的一個(gè)內(nèi)置接口碗短,這個(gè)接口非常的有意思,從上面的過(guò)程來(lái)看题涨,要實(shí)現(xiàn)這個(gè)接口的功能應(yīng)該很復(fù)雜才對(duì)偎谁,但實(shí)際上,我們通過(guò)IDEA打開(kāi)它的源碼携栋,就會(huì)發(fā)現(xiàn):

1.JPG

里面啥也沒(méi)有搭盾,只是一個(gè)Serializable接口聲明。實(shí)際上婉支,這個(gè)接口它僅用作標(biāo)識(shí)實(shí)現(xiàn)該接口的類可以被序列化鸯隅。
3.JPG

在序列化對(duì)象的過(guò)程中,聲明實(shí)現(xiàn)該接口是必須的向挖,如果上圖中的類不實(shí)現(xiàn)Serializable這個(gè)接口蝌以,就會(huì)報(bào)出NotSerializationException這個(gè)異常。

2.JPG

在使用前應(yīng)該注意的幾個(gè)問(wèn)題

在實(shí)現(xiàn)序列化和反序列化的過(guò)程中何之,需要注意幾個(gè)問(wèn)題跟畅。

  1. 靜態(tài)變量在序列化過(guò)程中并不會(huì)被保存:序列化保存的是對(duì)象狀態(tài),而靜態(tài)變量是屬于類變量溶推,因此并不會(huì)被保存徊件。

  2. 一個(gè)類能不能序列化,就看它是否實(shí)現(xiàn)Serializable這個(gè)接口蒜危。并且如果子類的父類實(shí)現(xiàn)了該接口虱痕,那么這個(gè)子類也可以實(shí)現(xiàn)序列化。

  3. serialVersionUID:這是一個(gè)與可序列化的類相關(guān)聯(lián)的版本號(hào)辐赞,在反序列化的過(guò)程中部翘,這個(gè)序列號(hào)用來(lái)驗(yàn)證是不是該類進(jìn)行的序列化過(guò)程。匹配成功响委,則進(jìn)行反序列化新思,若serialVersionUID匹配不成功,那么就會(huì)拋出InvalidClassException這個(gè)異常赘风。雖然可以不人為設(shè)定這個(gè)數(shù)值夹囚,但該接口的API強(qiáng)烈建議開(kāi)發(fā)者自行對(duì)這個(gè)版本號(hào)進(jìn)行顯式聲明,并且規(guī)定這個(gè)值必須是static類型和final類型邀窃。通常一般我們也會(huì)將其設(shè)為priavte私有變量的表述方式崔兴,例如:priavte static final long serialVersionUID = 1L

  4. Transient關(guān)鍵字控制的變量不會(huì)序列化到目標(biāo)文件中,當(dāng)反序列化完成后,這個(gè)Transient修飾的變量將被賦予Java規(guī)定的初始值敲茄。例如在下圖中位谋,字符串gender被賦予了默認(rèn)值null

    4.JPG

5.JPG

如何持久化一個(gè)對(duì)象:序列化與反序列化?

以第一段中的User類為例堰燎,對(duì)這個(gè)User類的對(duì)象進(jìn)行序列化過(guò)程掏父。User類是一個(gè)簡(jiǎn)單的JaveBean,它實(shí)現(xiàn)了序列化的接口Serializable秆剪,意味著這個(gè)類可以序列化赊淑。其中gender屬性被transient修飾,意味著這個(gè)變量將不會(huì)被序列化仅讽。除此之外陶缺,serialVersionUID被設(shè)置為1L,注意long長(zhǎng)整型的后面需要加L修飾洁灵,并用大寫(xiě)字母L修飾饱岸,不要用小寫(xiě)l,避免與數(shù)字1發(fā)生混淆徽千。

public class User implements Serializable {
?
 private String name;
 private int age;
 private String work;
 private transient String gender;
 private String words;
 private static final long serialVersionUID = 1L;
?
 public String getName() {
 return name;
 }
?
 public void setName(String name) {
 this.name = name;
 }
?
 public int getAge() {
 return age;
 }
?
 public void setAge(int age) {
 this.age = age;
 }
?
 public String getWork() {
 return work;
 }
?
 public void setWork(String work) {
 this.work = work;
 }
?
 public String getGender() {
 return gender;
 }
?
 public void setGender(String gender) {
 this.gender = gender;
 }
?
 public String getWords() {
 return words;
 }
?
 public void setWords(String words) {
 this.words = words;
 }
?
 @Override
 public String toString() {
 return "User{" +
 "name='" + name + '\'' +
 ", age=" + age +
 ", work='" + work + '\'' +
 ", gender='" + gender + '\'' +
 ", words='" + words + '\'' +
 '}';
 }
}

在定義了User類后苫费,首先是利用該類創(chuàng)建一個(gè)對(duì)象小明。

User user = new User();
user.setName("小明");
user.setGender("male");
user.setAge(25);
user.setWork("Programmer");
user.setWords("hello");</pre>

然后是對(duì)象的序列化過(guò)程双抽,在這個(gè)步驟中需要ObjectOutputStream將對(duì)象的基本數(shù)據(jù)類型寫(xiě)入流中百框。ObjectOutputStream很獨(dú)特,它只能將支持Serializable接口的對(duì)象寫(xiě)入字節(jié)流牍汹。默認(rèn)的對(duì)象序列化機(jī)制寫(xiě)入字節(jié)流的內(nèi)容包括:對(duì)象的類铐维、類簽名、非靜態(tài)字段的值∩鞣疲現(xiàn)在我們需要把小明這個(gè)對(duì)象的狀態(tài)寫(xiě)入文本中加以保存嫁蛇。

//將對(duì)象寫(xiě)入字節(jié)流
ObjectOutputStream objectOutputStream;
try{
 // 將一個(gè)OutputStream對(duì)象作為構(gòu)造函數(shù)的參數(shù)創(chuàng)建對(duì)象,這里是文件對(duì)象
 objectOutputStream = new ObjectOutputStream(new FileOutputStream("tempFile"));
 // 將小明這個(gè)對(duì)象寫(xiě)入到這個(gè)文本中
 objectOutputStream.writeObject(user);
 //關(guān)閉流
 objectOutputStream.close();
}catch (IOException e){
 e.printStackTrace();
}

運(yùn)行程序钧嘶,就可以看到文件夾下生成了名為tempFile的文件棠众,這就完成了對(duì)小明這個(gè)對(duì)象序列化的過(guò)程琳疏。打開(kāi)文件查看里面的內(nèi)容有决,但是會(huì)發(fā)現(xiàn)我們并不明白這到底寫(xiě)的是什么,但是這個(gè)就是小明被序列化后的模樣空盼。

6.JPG

由于寫(xiě)入的是字節(jié)流书幕,因此對(duì)閱讀者并不友好。后續(xù)會(huì)用一種更為完善的方法來(lái)解決這種問(wèn)題揽趾。

在序列化之后台汇,我們又突然需要復(fù)原小明,這就必須進(jìn)行對(duì)象的反序列化過(guò)程。反序列化過(guò)程與序列化過(guò)程類似苟呐,它需要ObjectInputStream方法來(lái)讀入需要復(fù)原的字節(jié)流痒芝,然后調(diào)用readObject的方法轉(zhuǎn)換成對(duì)象。在這個(gè)過(guò)程中牵素,會(huì)對(duì)序列號(hào)serialVersionUID進(jìn)行校驗(yàn)工作严衬,以確保類型匹配正確。

File file = new File("tempFile");
ObjectInputStream objectInputStream;
try{
 // 將存儲(chǔ)小明對(duì)象的文件流作為參數(shù)來(lái)創(chuàng)建一個(gè)對(duì)象
 objectInputStream = new ObjectInputStream(new FileInputStream(file));
 // 從這個(gè)字節(jié)流中讀取對(duì)象笆呆,并強(qiáng)制轉(zhuǎn)換為User類對(duì)象
 User myUser = (User) objectInputStream.readObject();
 System.out.println(myUser);
}catch (IOException e){
 e.printStackTrace();
}catch (ClassNotFoundException e){
 e.printStackTrace();
}

從控制臺(tái)中查看復(fù)原的小明请琳,看看他和我們序列化之前有什么區(qū)別:

7.JPG

可以看出,小明的gender因?yàn)楸?code>transient修飾赠幕,并沒(méi)有被序列化俄精,因此反序列化后字段被賦予了默認(rèn)值null,就這樣榕堰,小明被反序列化為一個(gè)身份不明的人竖慧。

序列化和反序列化在實(shí)際應(yīng)用中十分廣泛,例如遠(yuǎn)程通信傳輸對(duì)象信息的時(shí)候局冰,就需要將對(duì)象轉(zhuǎn)換成網(wǎng)絡(luò)所允許的二進(jìn)制字節(jié)流测蘑。但是在上面我們也發(fā)現(xiàn),保存到本地的對(duì)象文本由于編碼問(wèn)題康二,導(dǎo)致可讀性很差碳胳,有沒(méi)有什么更好的替代方案呢?

Json與FastJson庫(kù)

Json是一種輕量級(jí)的數(shù)據(jù)交換格式沫勿,在實(shí)際的工作中挨约,Json的使用非常的頻繁。Json通過(guò)鍵值對(duì)來(lái)表示對(duì)象的屬性和屬性值产雹。如下的Demo.json展示了Json文件的一般形式诫惭。

8.JPG

Json文件的特點(diǎn)在于結(jié)構(gòu)清晰,簡(jiǎn)明易讀蔓挖。并且在序列化與反序列化上夕土,Json使用起來(lái)也更方便。在這個(gè)問(wèn)題上瘟判,阿里巴巴提供了一個(gè)非常好用的工具FastJson怨绣,它是阿里巴巴開(kāi)源的Json解析庫(kù),并且支持將JavaBean序列化為Json字符串拷获,也可以從Json字符串反序列為JavaBean篮撑。

FastJson使用非常簡(jiǎn)單,如果不是使用Maven構(gòu)建項(xiàng)目匆瓜,那就需要下載FastJson的Jar包進(jìn)行導(dǎo)入赢笨,在IDEA中按照如下步驟添加FastJson庫(kù)

  • 點(diǎn)擊IDEA的File未蝌,彈出后選擇Project Structure

  • 在彈出的頁(yè)面選擇Modules,并在隨后的頁(yè)面中選擇Dependencies

  • Dependencies頁(yè)面下點(diǎn)擊右側(cè)綠色的+號(hào)茧妒,彈出的小菜單中選擇JARs or directories

  • 導(dǎo)入自己路徑下的FastJson的Jar包萧吠,點(diǎn)擊Apply后點(diǎn)擊ok

  • 測(cè)試FastJson庫(kù)是否正常運(yùn)行

使用FastJson對(duì)JavaBean進(jìn)行序列化與反序列化簡(jiǎn)直不要太簡(jiǎn)單了,兩個(gè)過(guò)程都只需要一行代碼便可以完成相關(guān)操作桐筏。例如FastJson對(duì)小明進(jìn)行序列化怎憋,見(jiàn)如下代碼:

User user = new User();
user.setName("小明");
user.setGender("male");
user.setAge(25);
user.setWork("Programmer");
user.setWords("hello");
?
String jsonData = JSONObject.toJSONString(user);
System.out.println(jsonData);

通過(guò)JSONObject調(diào)用toJSONString的方法,傳入需要序列化的對(duì)象九昧,只需要一行代碼绊袋,就可以得到對(duì)象序列化之后的字符串。通過(guò)打印就可以看出這個(gè)對(duì)象里面到底有些啥铸鹰。

9.JPG

依舊可以看出癌别,被transient修飾的gender并沒(méi)有被序列化,而這一次蹋笼,我們很直觀的看到了小明這個(gè)對(duì)象對(duì)序列化之后的結(jié)果展姐。

那么反序列化過(guò)程也同樣簡(jiǎn)潔:

User myUser = JSONObject.parseObject(jsonData, User.class);
System.out.println(myUser);

反序列化過(guò)程的parseObject方法接受一個(gè)序列化后的字符串和目標(biāo)對(duì)象的類,生成一個(gè)反序列化的對(duì)象后剖毯,通過(guò)User聲明的myUser引用反序列化后的對(duì)象圾笨,打印這個(gè)對(duì)象,得到如下的結(jié)果:

10.JPG

注意這個(gè)對(duì)象的輸出結(jié)果與上面序列化后的結(jié)果的差異逊谋,gender反序列化后被賦予了null擂达,其實(shí)這也說(shuō)明了反序列化過(guò)程按照類的模板進(jìn)行完全匹配的,對(duì)于沒(méi)能序列化的對(duì)象狀態(tài)胶滋,反序列化都盡量來(lái)恢復(fù)對(duì)象的初始狀態(tài)板鬓。

由此可以看出,Json文件和FastJson庫(kù)在Java的序列化和反序列化的作用效率會(huì)更高究恤,并且JavaBean不需要實(shí)現(xiàn)序列化Serializable接口俭令,直接就配合FastJson可以使用,大幅提升了編碼質(zhì)量部宿。

總結(jié)

在實(shí)際工作場(chǎng)景中抄腔,序列化和反序列化是一個(gè)十分常見(jiàn)常用的功能,現(xiàn)如今大部分都在采納Json和FastJson解析庫(kù)結(jié)合的這種方式進(jìn)行交互理张,但是對(duì)于Java中Serializable的基本性質(zhì)還是需要了解赫蛇。更深入的,對(duì)于Serializable中的writeObject方法和readObject方法實(shí)現(xiàn)自定義的序列化策略也還需要進(jìn)一步的研究和實(shí)踐涯穷,后續(xù)會(huì)及時(shí)更新棍掐。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末藏雏,一起剝皮案震驚了整個(gè)濱河市拷况,隨后出現(xiàn)的幾起案子作煌,更是在濱河造成了極大的恐慌,老刑警劉巖赚瘦,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粟誓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡起意,警方通過(guò)查閱死者的電腦和手機(jī)鹰服,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)揽咕,“玉大人悲酷,你說(shuō)我怎么就攤上這事∏咨疲” “怎么了设易?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蛹头。 經(jīng)常有香客問(wèn)我顿肺,道長(zhǎng),這世上最難降的妖魔是什么渣蜗? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任屠尊,我火速辦了婚禮,結(jié)果婚禮上耕拷,老公的妹妹穿的比我還像新娘讼昆。我一直安慰自己,他們只是感情好骚烧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布控淡。 她就那樣靜靜地躺著,像睡著了一般止潘。 火紅的嫁衣襯著肌膚如雪掺炭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天凭戴,我揣著相機(jī)與錄音涧狮,去河邊找鬼。 笑死么夫,一個(gè)胖子當(dāng)著我的面吹牛者冤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播档痪,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涉枫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了腐螟?” 一聲冷哼從身側(cè)響起愿汰,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤困后,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后衬廷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體摇予,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年吗跋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侧戴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跌宛,死狀恐怖酗宋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情疆拘,我是刑警寧澤本缠,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站入问,受9級(jí)特大地震影響丹锹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芬失,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一楣黍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棱烂,春花似錦租漂、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至衬鱼,卻和暖如春业筏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸟赫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工蒜胖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抛蚤。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓台谢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親岁经。 傳聞我的和親對(duì)象是個(gè)殘疾皇子朋沮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 10,837評(píng)論 0 24
  • Valentine 轉(zhuǎn)載請(qǐng)標(biāo)明出處樊拓。 序列化的意義 Java 平臺(tái)允許我們?cè)趦?nèi)存中創(chuàng)建可復(fù)用的Java 對(duì)象纠亚,但一...
    valentine_liang閱讀 875評(píng)論 0 0
  • 一、 序列化和反序列化概念 Serialization(序列化)是一種將對(duì)象以一連串的字節(jié)描述的過(guò)程骑脱;反序列化de...
    步積閱讀 1,437評(píng)論 0 10
  • 圖文原創(chuàng):水木空影 轉(zhuǎn)載請(qǐng)說(shuō)明出處! 文集有五:《手寫(xiě)心語(yǔ)》《親情友情》《嵐皋有家》《身處職場(chǎng)》《混自媒體》苍糠,持續(xù)...
    水木空影閱讀 432評(píng)論 6 6
  • 小時(shí)候叁丧,受有閱讀習(xí)慣的爸媽影響,我和姐姐對(duì)書(shū)有種說(shuō)不清楚的喜歡岳瞭。我?guī)缀蹩幢榱怂心芤?jiàn)得到的童話書(shū)拥娄,我的開(kāi)篇讀物是一...
    lulufly閱讀 374評(píng)論 0 0