序列化和反序列化的概念
- 序列化:把java對象轉(zhuǎn)換為字節(jié)序列的過程稱為對象的序列化,這些字節(jié)序列可以被保存在磁盤上或通過網(wǎng)絡(luò)傳輸,以備以后重新恢復原來的對象
- 反序列化:把字節(jié)序列恢復為對象的過程稱為對象的反序列化命斧。序列化機制使得對象可以脫離程序的運行而獨立存在
序列化的功能/用途
- 持久化對象:Java平臺允許我們在內(nèi)存中創(chuàng)建可復用的Java對象田晚,但一般情況下,只有當JVM處于運行時国葬,這些對象才可能存在贤徒,即碍讯,
這些對象的生命周期不會比JVM的生命周期更長脖咐。但在現(xiàn)實應(yīng)用中,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象兼蜈,并在將來重新讀取被保存的對象通孽。把對象的字節(jié)序列永久地保存到硬盤上序宦,通常存放在一個文件中,以此實現(xiàn)該功能 背苦。java中的對象的內(nèi)部狀態(tài)只保存在內(nèi)存中互捌,其生命周期最長與JVM的生命周期一樣,即JVM停止之后行剂,所有對象都會被銷毀秕噪。 - 網(wǎng)絡(luò)傳輸:在網(wǎng)絡(luò)上傳送對象的字節(jié)序列。
實際應(yīng)用
- 在很多應(yīng)用中厚宰,需要對某些對象進行序列化腌巾,讓它們離開內(nèi)存空間,入住物理硬盤,以便長期保存壤躲。比如最常見的是Web服務(wù)器中的Session對象城菊,當有 10萬用戶并發(fā)訪問备燃,就有可能出現(xiàn)10萬個Session對象碉克,內(nèi)存可能吃不消,于是Web容器就會把一些seesion先序列化到硬盤中并齐,等要用了漏麦,再把保存在硬盤中的對象還原到內(nèi)存中。
- 當兩個進程在進行遠程通信時况褪,彼此可以發(fā)送各種類型的數(shù)據(jù)撕贞。無論是何種類型的數(shù)據(jù),都會以二進制序列的形式在網(wǎng)絡(luò)上傳送测垛。發(fā)送方需要把這個Java對象轉(zhuǎn)換為字節(jié)序列捏膨,才能在網(wǎng)絡(luò)上傳送;接收方則需要把字節(jié)序列再恢復為Java對象食侮。
實現(xiàn)
- java.io.Serializable接口号涯,那么它就可以被序列化
- Externalizable:
Serializable接口
· 優(yōu)點:內(nèi)建支持
· 優(yōu)點:易于實現(xiàn)
· 缺點:占用空間過大
· 缺點:由于額外的開銷導致速度變比較慢
Externalizable接口
· 優(yōu)點:開銷較少(程序員決定存儲什么)
· 優(yōu)點:可能的速度提升
· 缺點:虛擬機不提供任何幫助,也就是說所有的工作都落到了開發(fā)人員的肩上锯七。
在兩者之間如何選擇要根據(jù)應(yīng)用程序的需求來定链快。Serializable通常是最簡單的解決方案,但是它可能會導致出現(xiàn)不可接受的性能問題或空間問題眉尸;在出現(xiàn)這些問題的情況下域蜗,Externalizable可能是一條可行之路。
JDK類庫中的序列化API
- java.io.ObjectOutputStream代表對象輸出流噪猾,它的writeObject(Object obj)方法可對參數(shù)指定的obj對象進行序列化霉祸,把得到的字節(jié)序列寫到一個目標輸出流中。
- java.io.ObjectInputStream代表對象輸入流袱蜡,它的readObject()方法從一個源輸入流中讀取字節(jié)序列丝蹭,再把它們反序列化為一個對象,并將其返回戒劫。
序列化與反序列化的編程實現(xiàn)
實現(xiàn)序列化接口的類
public class Person implements Serializable{
private static final long serialVersionUID = -1015228989208411177L;
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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 class WriteObject {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try {
//1.創(chuàng)建一個ObjectOutputStream
oos = new ObjectOutputStream(new FileOutputStream("/home/sunyan/object.txt"));
Person per = new Person("孫悟空", 500);
//2.將per對象寫入輸入流
oos.writeObject(per);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(oos != null){
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
反序列化
public class ReadObject {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
//1.創(chuàng)建一個ObjectInputStream輸入流
ois = new ObjectInputStream(new FileInputStream("/home/sunyan/object.txt"));
//2.從輸入流中讀取一個Java對象半夷,并將其強制類型轉(zhuǎn)換為Person對象
Person p = (Person) ois.readObject();
System.out.println("名字為:" + p1.getName() + "\n年齡為:" + p1.getAge());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
try {
if (ois == null) {
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
執(zhí)行結(jié)果:
- 如果我們向文件中使用序列化機制寫入了多個Java對象,使用反序列化機制恢復對象必須按照實際寫入的順序讀取迅细。
序列化
Person per1 = new Person("孫悟空", 500);
Person per2 = new Person("孫小妹", 50);
oos.writeObject(per1);
oos.writeObject(per2);
反序列化
Person p1 = (Person) ois.readObject();
Person p2 = (Person) ois.readObject();
System.out.println("名字為:" + p1.getName() + "\n年齡為:" + p1.getAge());
System.out.println("名字為:" + p2.getName() + "\n年齡為:" + p2.getAge());
執(zhí)行結(jié)果
- 對象引用的序列化
如果類的屬性不是基本類型或者String類型巫橄,而是另一個引用類型,那么這個引用類型必須是可序列化的茵典,否則該類也是不可序列化的湘换,即使該類實現(xiàn)了Serializable,Externalizable接口。
public class Teacher implements Serializable{
private String name;
//類的屬性是引用類型彩倚,也必須序列化筹我。
//如果Person是不可序列化的,無論Teacher實現(xiàn)Serializable,Externalizable接口帆离,則Teacher
//都是不可序列化的蔬蕊。
private Person student;
public Teacher(String name, Person student) {
super();
this.name = name;
this.student = student;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getStudent() {
return student;
}
public void setStudent(Person student) {
this.student = student;
}
}
上述代碼中,Teacher中有一個引用類型student,若Person未實現(xiàn)接口Serializable哥谷。即
public class Person implements Serializable{
}
則在序列化過程中岸夯,會報錯
- Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字们妥,可以阻止該變量被序列化到文件中猜扮,在被反序列化后,transient 變量的值被設(shè)為初始值监婶,如 int 型的是 0旅赢,對象型的是 null。
在Person中更改如下代碼
private transient int age;
此時1中的代碼惑惶,經(jīng)序列化和反序列化后煮盼,執(zhí)行結(jié)果如下
-
s?e?r?i?a?l?V?e?r?s?i?o?n?U?I?D
s?e?r?i?a?l?V?e?r?s?i?o?n?U?I?D?:? ?字?面?意?思?上?是?序?列?化?的?版?本?號?,凡是實現(xiàn)Serializable接口的類都有一個表示序列化版本標識符的靜態(tài)變量集惋。序列化 ID 是否一致孕似,決定 虛擬機是否允許反序列化。
實現(xiàn)Serializable接口的類如果類中沒有添加serialVersionUID喉祭,那么就會出現(xiàn)如下的警告提示
serialVersionUID有兩種生成方式:
采用 Add default serial version ID
這種方式生成的serialVersionUID是1L,例如:
private static final long serialVersionUID = 1L;
采用 Add generated serial version ID這種方式生成的serialVersionUID是根據(jù)類名雷绢,接口名,方法和屬性等來生成的翘紊,例如:
private static final long serialVersionUID = -1015228989208411177L;
對1中的代碼序列化后,修改
private static final long serialVersionUID = -1015228989208411177L;
為
private static final long serialVersionUID = -1015228989208411178L;
此時帆疟,進行反序列化,會報錯
反序列化漏洞危害
當應(yīng)用代碼從用戶接受序列化數(shù)據(jù)踪宠,并試圖反序列化改數(shù)據(jù)進行下一步處理時,會產(chǎn)生反序列化漏洞柳琢,其中最有危害性的就是遠程代碼注入润脸。
這種漏洞產(chǎn)生原因是,java類ObjectInputStream在執(zhí)行反序列化時他去,并不會對自身的輸入進行檢查,這就說明惡意攻擊者可能也可以構(gòu)建特定的輸入灾测,在 ObjectInputStream類反序列化之后會產(chǎn)生非正常結(jié)果爆价,利用這一方法就可以實現(xiàn)遠程執(zhí)行任意代碼行施。
最后再加一些相關(guān)知識點
1允坚、聲明為static和transient的成員數(shù)據(jù)不能被串行化魂那,因為static代表類的狀態(tài),transient代表對象的臨時數(shù)據(jù)蛾号。
2、要想將父類對象也序列化涯雅,就需要讓父類也實現(xiàn)Serializable 接口鲜结。
3、服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù)活逆,對象中有一些數(shù)據(jù)是敏感的精刷,比如密碼字符串等,希望對該密碼字段在序列化時蔗候,進行加密怒允,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時锈遥,才可以對密碼進行讀取纫事,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全。