這些序列化與反序列化的坑你都踩過嗎?
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進(jìn)!你不來,我和你的競爭對手一起精進(jìn)!
編輯:業(yè)余草
liuchenyang0515.blog.csdn.net
推薦:https://www.xttblog.com/?p=5317
序列化與反序列化的概念
先說說序列化和反序列化的概念
?序列化:將對象寫入到
?IO流中
反序列化:從IO流中恢復(fù)對象
Serializable接口是一個(gè)「標(biāo)記接口」,不用實(shí)現(xiàn)任何方法,標(biāo)記當(dāng)前類對象是可以序列化的,是給JVM看的。
序列化機(jī)制允許將這些實(shí)現(xiàn)序列化接口的對象轉(zhuǎn)化為字節(jié)序列,這些字節(jié)序列可以保證在磁盤上或者網(wǎng)絡(luò)傳輸后恢復(fù)成原來的對象。序列化就是把對象存儲(chǔ)在JVM以外的地方,序列化機(jī)制可以讓對象脫離程序的運(yùn)行而獨(dú)立存在。
序列化在業(yè)務(wù)代碼也許用的不多,但是在框架層面用的是很多的。
相關(guān)技術(shù):Session的序列化或者反序列化
先給出序列化的例子,請記住這個(gè)People類,后面會(huì)根據(jù)這個(gè)類來改造講解。
public?class?People?{
????private?Long?id;
????public?People(Long?id)?{
????????this.id?=?id;
????}
????public?Long?getId()?{
????????return?id;
????}
????public?void?setId(Long?id)?{
????????this.id?=?id;
????}
????@Override
????public?String?toString()?{
????????return?"People{"?+
????????????????"id="?+?id?+
????????????????'}';
????}
}
import?java.io.*;
//?屏蔽編譯器的警告
@SuppressWarnings("all")
public?class?Main?{
????/**
?????*?序列化和反序列化?People?對象
?????*/
????private?static?void?testSerializablePeople()?throws?Exception?{
????????//?序列化的步驟
????????//?用于存儲(chǔ)序列化的文件,這里的java_下劃線僅僅為了說明是java序列化對象,沒有任何其他含義
????????File?file?=?new?File("/tmp/people_10.java_");
????????if?(!file.exists())?{
????????????//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄
????????????file.getParentFile().mkdirs();
????????????try?{
????????????????//?2,再創(chuàng)建文件
????????????????file.createNewFile();
????????????}?catch?(IOException?e)?{
????????????????e.printStackTrace();
????????????}
????????}
????????People?p?=?new?People(10L);
????????//?創(chuàng)建一個(gè)輸出流
????????ObjectOutputStream?oos?=?new?ObjectOutputStream(
????????????????new?FileOutputStream(file)
????????);
????????//?輸出可序列化對象
????????oos.writeObject(p);
????????//?關(guān)閉輸出流
????????oos.close();
????????//?反序列化的步驟
????????//?創(chuàng)建一個(gè)輸入流
????????ObjectInputStream?ois?=?new?ObjectInputStream(
????????????????new?FileInputStream(file)
????????);
????????//?得到反序列化的對象,這里可以強(qiáng)轉(zhuǎn)為People類型
????????Object?newPerson?=?ois.readObject();
????????//?關(guān)閉輸入流
????????ois.close();
????????System.out.println(newPerson);
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????testSerializablePeople();
????}
}
運(yùn)行之后,看到磁盤文件因?yàn)樾蛄谢嗔艘粋€(gè)文件

控制臺中因反序列化輸出的對象信息打印如下:

子類序列化
子類實(shí)現(xiàn)Serializable接口,父類沒有實(shí)現(xiàn),子類可以序列化嗎?
去掉父類People的implements Serializable,讓父類不實(shí)現(xiàn)序列化接口,子類Worker實(shí)現(xiàn)序列化接口
public?class?Worker?extends?People?implements?Serializable?{
????private?String?name;
????private?Integer?age;
????public?Worker(Long?id,?String?name,?Integer?age)?{
????????super(id);
????????this.name?=?name;
????????this.age?=?age;
????}
}
public?static?void?main(String[]?args)?throws?Exception?{
????testSerizableWorker();
}
/**
?*?子類實(shí)現(xiàn)序列化,?父類不實(shí)現(xiàn)序列化
?*?*/
private?static?void?testSerizableWorker()?throws?Exception?{
????File?file?=?new?File("/tmp/worker_10.java_");
????if?(!file.exists())?{
????????//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄
????????file.getParentFile().mkdirs();
????????try?{
????????????//?2,再創(chuàng)建文件
????????????file.createNewFile();
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
????Worker?p?=?new?Worker(10L,?"lcy",?18);
????//?創(chuàng)建一個(gè)輸出流
????ObjectOutputStream?oos?=?new?ObjectOutputStream(
????????????new?FileOutputStream(file)
????);
????//?輸出可序列化對象
????oos.writeObject(p);
????//?關(guān)閉輸出流
????oos.close();
????ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(file));
????Object?newWorker?=?ois.readObject();?//?父類沒有序列化的時(shí)候,需要調(diào)用父類的無參數(shù)構(gòu)造方法
????ois.close();
????System.out.println(newWorker);
}
再次測試運(yùn)行

結(jié)果顯示沒有有效地構(gòu)造器,原來是因?yàn)楦割悰]有序列化的時(shí)候,Object newWorker = ois.readObject()需要直接調(diào)用父類的無參數(shù)構(gòu)造方法,不經(jīng)過子類的無參構(gòu)造方法。
我們在父類People中加上空的構(gòu)造方法之后再次執(zhí)行

結(jié)果卻發(fā)現(xiàn)打印的不是Worker,而是父類People,因?yàn)樽宇悰]有實(shí)現(xiàn)toString而調(diào)用父類的toString,所以打印了People對象,至于父類成員變量id為什么是null,原因如下:
?一個(gè)子類實(shí)現(xiàn)了
Serializable接口,它的父類都沒有實(shí)現(xiàn)Serializable接口,序列化該子類對象。要想反序列化后輸出父類定義的某變量的數(shù)值,就需要讓父類也實(shí)現(xiàn)Serializable接口或者父類有默認(rèn)的無參的構(gòu)造函數(shù)。在父類沒有實(shí)現(xiàn)
Serializable接口時(shí),虛擬機(jī)是不會(huì)序列化父對象的,而一個(gè)Java對象的構(gòu)造必須先有父對象,才有子對象,反序列化也不例外。所以反序列化時(shí),為了構(gòu)造父對象,只能調(diào)用父類的無參構(gòu)造函數(shù)作為默認(rèn)的父對象。因此當(dāng)我們?nèi)「笇ο蟮淖兞恐禃r(shí),它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值,如果在父類無參構(gòu)造函數(shù)中沒有對變量賦值,那么父類成員變量值都是默認(rèn)值,如這里的Long型就是null。根據(jù)以上特性,我們可以將不需要被序列化的字段抽取出來放到父類中,子類實(shí)現(xiàn)
?Serializable接口,父類不實(shí)現(xiàn)Serializable接口但提供一個(gè)空構(gòu)造方法,則父類的字段數(shù)據(jù)將不被序列化。
最后加上子類Worker的toString方法,打印結(jié)果如下:

總結(jié):
子類實(shí)現(xiàn) Serializable接口,父類沒有實(shí)現(xiàn),子類「可以」序列化?。?/section>這種情況父類一定要提供空構(gòu)造方法,不要忘了子類的 toString方法!
引用對象的序列化
類中存在引用對象,這個(gè)類對象在什么情況下可以實(shí)現(xiàn)序列化?
來一個(gè)組合對象,里面引用People對象,此時(shí)People對象「沒有」實(shí)現(xiàn)Serializable接口,能否序列化呢?代碼給出來,大家可以自行復(fù)制測試一下。
public?class?Combo?implements?Serializable?{
????private?int?id;
????private?People?people;
????public?Combo(int?id,?People?people)?{
????????this.id?=?id;
????????this.people?=?people;
????}
????public?int?getId()?{
????????return?id;
????}
????public?void?setId(int?id)?{
????????this.id?=?id;
????}
????public?People?getPeople()?{
????????return?people;
????}
????public?void?setPeople(People?people)?{
????????this.people?=?people;
????}
????
????@Override
????public?String?toString()?{
????????return?"Combo{"?+
????????????????"id="?+?id?+
????????????????",?people="?+?people?+
????????????????'}';
????}
}
public?class?People?{
????private?Long?id;
????public?People()?{
????}
????public?People(Long?id)?{
????????this.id?=?id;
????}
????public?Long?getId()?{
????????return?id;
????}
????public?void?setId(Long?id)?{
????????this.id?=?id;
????}
????@Override
????public?String?toString()?{
????????return?"People{"?+
????????????????"id="?+?id?+
????????????????'}';
????}
}
private?static?void?testSerializableCombo()?throws?Exception?{
????File?file?=?new?File("/tmp/combo_10.java_");
????if?(!file.exists())?{
????????//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄
????????file.getParentFile().mkdirs();
????????try?{
????????????//?2,再創(chuàng)建文件
????????????file.createNewFile();
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
????Combo?p?=?new?Combo(1,?new?People(10L));
????//?創(chuàng)建一個(gè)輸出流
????ObjectOutputStream?oos?=?new?ObjectOutputStream(
????????????new?FileOutputStream(file)
????);
????//?輸出可序列化對象
????oos.writeObject(p);
????//?關(guān)閉輸出流
????oos.close();
????ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(file));
????Object?newCombo?=?ois.readObject();
????ois.close();
????System.out.println(newCombo);
}
public?static?void?main(String[]?args)?throws?Exception?{
????testSerializableCombo();
}
運(yùn)行結(jié)果如下

直接爆出異常,說明People類沒有序列化。當(dāng)People加上implements Serializable實(shí)現(xiàn)序列化接口后,再次執(zhí)行如下

總結(jié):
?一個(gè)類里面所有的屬性必須是可序列化的,這個(gè)類才能順利的序列化。比如,類中存在引用對象,那么這個(gè)引用對象必須是可序列化的,這個(gè)類才能序列化。
?
同一個(gè)對象多次序列化
同一個(gè)對象多次序列化之間有屬性更新,前后的序列化有什么區(qū)別?
下面例子中People是可序列化的,每次序列化之前都會(huì)把People的id值修改了,用來觀察看看,多次序列化期間,如果對象屬性更新,是否會(huì)影響序列化,反序列化有什么區(qū)別。
/**
?*?同一個(gè)對象多次序列化的問題,?坑
?*?*/
private?static?void?sameObjectRepeatedSerialization()?throws?Exception?{
????File?file?=?new?File("/tmp/peopele_more.java_");
????if?(!file.exists())?{
????????//?1,先得到文件的上級目錄,并創(chuàng)建上級目錄
????????file.getParentFile().mkdirs();
????????try?{
????????????//?2,再創(chuàng)建文件
????????????file.createNewFile();
????????}?catch?(IOException?e)?{
????????????e.printStackTrace();
????????}
????}
????People?p?=?new?People(10L);
????ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream(file));
????//?未序列化,先修改屬性
????p.setId(11L);
????oos.writeObject(p);
????//?序列化一次后,再次修改屬性
????p.setId(15L);
????oos.writeObject(p);
????//?序列化兩次后,再次修改屬性
????p.setId(20L);
????oos.writeObject(p);
????oos.close();
????ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(file));
????Object?people1?=?ois.readObject();
????Object?people2?=?ois.readObject();
????Object?people3?=?ois.readObject();
????ois.close();
????System.out.println(((People)?people1).getId());
????System.out.println(((People)?people2).getId());
????System.out.println(((People)?people3).getId());
}
public?static?void?main(String[]?args)?throws?Exception?{
????sameObjectRepeatedSerialization();
}
運(yùn)行結(jié)果如下

結(jié)果發(fā)現(xiàn)反序列化讀出的值都是一樣的。說明當(dāng)對象第一次序列化成功后,后續(xù)這個(gè)對象屬性即使有修改,也不會(huì)對后面的序列化造成成影響。
這其實(shí)是序列化算法的原因,所有要序列化的對象都有一個(gè)序列化的編碼號,當(dāng)試圖序列化一個(gè)對象,會(huì)檢查這個(gè)對象是否已經(jīng)序列化過,若從未序列化過,才會(huì)序列化為字節(jié)序列去輸出。若已經(jīng)序列化過,則會(huì)輸出一個(gè)編碼符號,不會(huì)重復(fù)序列化一個(gè)對象。如下

序列化一次后,后續(xù)繼續(xù)序列化并未重復(fù)轉(zhuǎn)換為字節(jié)序列,而是輸出字符q~
總結(jié):
?當(dāng)?shù)谝淮涡蛄谢?,不管如何修改這個(gè)對象的屬性,都不會(huì)對后續(xù)的序列化產(chǎn)生影響,反序列化的結(jié)果都和第一次相同。
?
歡迎一鍵三連~
