聲明:原創(chuàng)文章愉豺,轉(zhuǎn)載請(qǐng)注明出處外潜。http://www.reibang.com/u/e02df63eaa87
1、序列化概念
序列化:把對(duì)象轉(zhuǎn)換為字節(jié)序列的過程友绝。
反序列化:把字節(jié)序列恢復(fù)為對(duì)象的過程抛腕。
對(duì)象的序列化主要有兩種用途
- 把對(duì)象的字節(jié)序列永久地保存到硬盤上芋绸,通常存放在一個(gè)文件中媒殉;
- 在網(wǎng)絡(luò)上傳送對(duì)象的字節(jié)序列担敌。
在很多應(yīng)用中,需要對(duì)某些對(duì)象進(jìn)行序列化廷蓉,讓它們離開內(nèi)存空間全封,入住物理硬盤,以便長(zhǎng)期保存桃犬。
當(dāng)兩個(gè)進(jìn)程在進(jìn)行遠(yuǎn)程通信時(shí)刹悴,彼此可以發(fā)送各種類型的數(shù)據(jù)。無論是何種類型的數(shù)據(jù)攒暇,都會(huì)以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送土匀。發(fā)送方需要把這個(gè)Java對(duì)象轉(zhuǎn)換為字節(jié)序列,才能在網(wǎng)絡(luò)上傳送形用;接收方則需要把字節(jié)序列再恢復(fù)為Java對(duì)象就轧。
2、JDK中的序列化API
2.1 輸入輸出流
java.io.ObjectOutputStream代表對(duì)象輸出流田度,它的writeObject(Object obj)方法可對(duì)參數(shù)指定的obj對(duì)象進(jìn)行序列化妒御,把得到的字節(jié)序列寫到一個(gè)目標(biāo)輸出流中。
java.io.ObjectInputStream代表對(duì)象輸入流镇饺,它的readObject()方法從一個(gè)源輸入流中讀取字節(jié)序列乎莉,再把它們反序列化為一個(gè)對(duì)象,并將其返回。
對(duì)象序列化包括如下步驟:
- 創(chuàng)建一個(gè)對(duì)象輸出流惋啃,它可以包裝一個(gè)其他類型的目標(biāo)輸出流哼鬓,如文件輸出流;
- 通過對(duì)象輸出流的writeObject()方法寫對(duì)象边灭。
對(duì)象反序列化的步驟如下:
- 創(chuàng)建一個(gè)對(duì)象輸入流魄宏,它可以包裝一個(gè)其他類型的源輸入流,如文件輸入流存筏;
- 通過對(duì)象輸入流的readObject()方法讀取對(duì)象宠互。
2.2 Serializable接口
并不是每個(gè)對(duì)象都能寫到輸出流⊥旨幔可以寫入輸出流的對(duì)象稱為可序列化的予跌。可序列化的對(duì)象是java.io.Serializable接口的實(shí)例善茎。因此券册,可序列化對(duì)象必須實(shí)現(xiàn)Serializable接口。試圖存儲(chǔ)一個(gè)不支持Serializable接口的對(duì)象會(huì)引起一個(gè)NotSerializableException異常垂涯。
Serializable接口是一種標(biāo)記接口烁焙。由于它沒有任何方法,不需要在類中為實(shí)現(xiàn)Serializable接口增加代碼耕赘。實(shí)現(xiàn)這個(gè)接口可以啟動(dòng)Java的序列化機(jī)制骄蝇,自動(dòng)完成存儲(chǔ)對(duì)象和數(shù)組的過程。
只有實(shí)現(xiàn)了Serializable和Externalizable接口的類的對(duì)象才能被序列化操骡。Externalizable接口繼承自 Serializable接口九火,實(shí)現(xiàn)Externalizable接口的類完全由自身來控制序列化的行為,而僅實(shí)現(xiàn)Serializable接口的類可以 采用默認(rèn)的序列化方式 册招。
2.3 例子
Person類
public class Person implements Serializable {
private int id;
private int age;
private boolean sex;
private String name;
private String addr;
public Person(int id, int age, boolean sex, String name, String addr) {
this.id = id;
this.age = age;
this.sex = sex;
this.name = name;
this.addr = addr;
}
public Person() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isSex() {
return sex;
}
public void setSex(boolean sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", age=" + age +
", sex=" + sex +
", name='" + name + '\'' +
", addr='" + addr + '\'' +
'}';
}
}
TestSerializable 類
public class TestSerializable {
public static void main(String[] args) {
try {
Person person = new Person(1001, 18, true, "Jack", "BeiJing");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Person.dat")));
oos.writeObject(person);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Person.dat")));
Person p = (Person) ois.readObject();
System.out.println(p.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
運(yùn)行結(jié)果
2.4 transient關(guān)鍵字
- 如果一個(gè)對(duì)象是Serializable實(shí)例岔激,但包含了非序列化的成員,那這個(gè)對(duì)象是不可以被序列化的是掰。
- 如果一個(gè)對(duì)象序列化時(shí)虑鼎,對(duì)于其中的某個(gè)對(duì)象不希望其被序列化(敏感信息),即使對(duì)象中的成員是私有的键痛,通過序列化處理就可以通過文件或攔截網(wǎng)絡(luò)來訪問炫彩。
有一種方法可以防止敏感信息被序列化,將類實(shí)現(xiàn)為Externalizable散休,這樣媒楼,沒有任何東西可以自動(dòng)序列化,并且可以在writeExternal()內(nèi)部只對(duì)所需部分進(jìn)行顯式的序列化戚丸。
如果操作的是一個(gè)Serializable對(duì)象划址,序列化操作會(huì)自動(dòng)進(jìn)行扔嵌。為了能夠控制,可以使用transient關(guān)鍵字逐個(gè)字段關(guān)閉序列化夺颤。
例子:
public class Login implements Serializable {
private String username;
private Date date = new Date();
private transient String passuid;
public Login(String username, String passuid) {
this.username = username;
this.passuid = passuid;
}
@Override
public String toString() {
return "Login{" +
"date=" + date +
", username='" + username + '\'' +
", passuid='" + passuid + '\'' +
'}';
}
public static void main(String[] args) throws Exception {
Login login = new Login("Jack", "123456");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Login.dat")));
oos.writeObject(login);
oos.close();
TimeUnit.SECONDS.sleep(1);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Login.dat")));
Login a = (Login) ois.readObject();
System.out.println(a.toString());
}
}
可以看到痢缎,其中的date和username域是可見的,而passuid是transient的世澜,所以不會(huì)被自動(dòng)保存到磁盤独旷。另外,自動(dòng)序列化機(jī)制也不會(huì)嘗試去恢復(fù)它寥裂,當(dāng)對(duì)象被恢復(fù)時(shí)嵌洼,passuid域就會(huì)變成null。
由于Externalizable對(duì)象在默認(rèn)情況下不保存任何字段封恰,所以transient關(guān)鍵字只能和Serializable對(duì)象一起使用麻养。
2.5 serialVersionUID的作用
s?e?r?i?a?l?V?e?r?s?i?o?n?U?I?D?:? ?字?面?意?思?上?是?序?列?化?的?版?本?號(hào)?,凡是實(shí)現(xiàn)Serializable接口的類都有一個(gè)表示序列化版本標(biāo)識(shí)符的靜態(tài)變量诺舔。
private static final long serialVersionUID = 4603642343377807741L;
對(duì)于上面Login的例子鳖昌,增加private String ip;
,在對(duì)Login.dat
進(jìn)行反序列化:
public class Login implements Serializable {
private String username;
private Date date = new Date();
private transient String passuid;
private String ip;
public Login(String username, String passuid) {
this.username = username;
this.passuid = passuid;
}
@Override
public String toString() {
return "Login{" +
"date=" + date +
", username='" + username + '\'' +
", passuid='" + passuid + '\'' +
'}';
}
public static void main(String[] args) throws Exception {
Login login = new Login("Jack", "123456");
// 序列化
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Login.dat")));
// oos.writeObject(login);
// oos.close();
TimeUnit.SECONDS.sleep(1);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Login.dat")));
Login a = (Login) ois.readObject();
System.out.println(a.toString());
}
}
Exception in thread "main" java.io.InvalidClassException: gc.Login; local class incompatible: stream classdesc serialVersionUID = -4069397356839580213, local class serialVersionUID = 3104314666871190943
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
...
序列化運(yùn)行時(shí)使用serialVersionUID 版本號(hào)與每個(gè)可序列化類相關(guān)聯(lián)低飒,該序列號(hào)在反序列化過程中用于驗(yàn)證序列化對(duì)象的發(fā)送者和接收者是否為該對(duì)象加載了與序列化兼容的類许昨。如果接收者加載的該對(duì)象的類的 serialVersionUID 與對(duì)應(yīng)的發(fā)送者的類的版本號(hào)不同,則反序列化將會(huì)導(dǎo)致 InvalidClassException異常褥赊。
如果可序列化類未顯式聲明 serialVersionUID糕档,則序列化運(yùn)行時(shí)將基于該類的各個(gè)方面計(jì) 算該類的默認(rèn) serialVersionUID 值。不過崭倘,強(qiáng)烈建議所有可序列化類都顯式聲明 serialVersionUID 值翼岁,原因是計(jì)算默認(rèn)的 serialVersionUID 對(duì)類的詳細(xì)信息具有較高的敏感性,根據(jù)編譯器實(shí)現(xiàn)的不同可能千差萬別司光,這樣 在反序列化過程中可能會(huì)導(dǎo)致意外的 InvalidClassException。因此悉患,為保證 serialVersionUID 值跨不同 java 編譯器實(shí)現(xiàn)的一致性残家,序列化類必須聲明一個(gè)明確的 serialVersionUID 值。
因此售躁,指定serialVersionUID的值坞淮,重新進(jìn)行序列化:
public class Login implements Serializable {
private static final long serialVersionUID = 4603642343377807741L;
private String username;
private Date date = new Date();
private transient String passuid;
public Login(String username, String passuid) {
this.username = username;
this.passuid = passuid;
}
@Override
public String toString() {
return "Login{" +
"date=" + date +
", username='" + username + '\'' +
", passuid='" + passuid + '\'' +
'}';
}
public static void main(String[] args) throws Exception {
Login login = new Login("Jack", "123456");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./Login.dat")));
oos.writeObject(login);
oos.close();
}
加入新的字段,再次進(jìn)行反序列化:
public class Login implements Serializable {
private static final long serialVersionUID = 4603642343377807741L;
private String username;
private Date date = new Date();
private transient String passuid;
private String ip;
public Login(String username, String passuid) {
this.username = username;
this.passuid = passuid;
}
@Override
public String toString() {
return "Login{" +
"date=" + date +
", username='" + username + '\'' +
", passuid='" + passuid + '\'' +
'}';
}
public static void main(String[] args) throws Exception {
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./Login.dat")));
Login a = (Login) ois.readObject();
System.out.println(a.toString());
}
}
可以看到陪捷,Login{date=Thu Mar 09 09:27:35 CST 2017, username='Jack', passuid='null'}
回窘,說明反序列化成功了。
顯式地定義serialVersionUID有兩種用途:
- 在某些場(chǎng)合市袖,希望類的不同版本對(duì)序列化兼容啡直,因此需要確保類的不同版本具有相同的serialVersionUID烁涌;
- 在某些場(chǎng)合,不希望類的不同版本對(duì)序列化兼容酒觅,因此需要確保類的不同版本具有不同的serialVersionUID撮执。
引用
http://286.iteye.com/blog/2227942
http://www.cnblogs.com/xdp-gacl/p/3777987.html