java中的序列化
經(jīng)常聽(tīng)到關(guān)于序列化的話題,但是一直沒(méi)有理解什么是序列化,為什么要序列化。
首先百度了一下序列化的定義:序列化(Serialization)將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^(guò)程。在序列化期間,對(duì)象將其當(dāng)前狀態(tài)寫(xiě)入臨時(shí)或持久性存儲(chǔ)區(qū)。以后可以通過(guò)從存儲(chǔ)區(qū)中讀取或反序列化對(duì)象的狀態(tài),重新創(chuàng)建該對(duì)象。
在java中,存在于java虛擬機(jī)中的對(duì)象,他的內(nèi)部狀態(tài)只保存在內(nèi)存中。如果jvm停止了這些狀態(tài)就會(huì)丟失。在java中實(shí)現(xiàn)基本的對(duì)象序列化是件很簡(jiǎn)單的事。需要序列化的java類(lèi)只需要實(shí)現(xiàn)java.io.Serializable接口即可。這個(gè)接口只是作為一個(gè)標(biāo)識(shí),表示這個(gè)類(lèi)可以進(jìn)行序列化和反序列化。
序列化的特點(diǎn):如果一個(gè)類(lèi)能夠被序列化,那么他的子類(lèi)也可以被序列化。這里有個(gè)問(wèn)題就是:如果子類(lèi)需要序列化的話,還是要顯式的聲明serialVersionUID。在反序列化的時(shí)候聲明為static和transient類(lèi)型的成員變量不能被序列化。static表示類(lèi)的狀態(tài),transient表示對(duì)象的臨時(shí)數(shù)據(jù)。
序列化的運(yùn)用場(chǎng)景:
1.需要把內(nèi)存中的對(duì)象狀態(tài)保存到一個(gè)文件或者數(shù)據(jù)庫(kù)中。
2.需要使用套接字在網(wǎng)絡(luò)傳輸對(duì)象。
3.需要使用RMI傳輸對(duì)象。
java對(duì)象序列化不僅保留一個(gè)對(duì)象的數(shù)據(jù),而且遞歸保存對(duì)象引用的每個(gè)對(duì)象的數(shù)據(jù)。當(dāng)然這些屬性對(duì)象也需要實(shí)現(xiàn)接口java.io.Serializable。
實(shí)際的序列化和反序列化工作是通過(guò)ObjectOutputStream和ObjectInputStream來(lái)完成。ObjectOutputStream的writeObject方法可以把一個(gè)java對(duì)象寫(xiě)到流中。ObjectInputStream的readObject方法可以從流中讀取一個(gè)java對(duì)象。雖然用的參數(shù)或者返回值都是單個(gè)對(duì)象,但是實(shí)際操作的是一個(gè)對(duì)象圖。包括了這個(gè)對(duì)象狀態(tài)所引用的其他對(duì)象,以及具有相關(guān)關(guān)系的對(duì)象。會(huì)自動(dòng)遍歷這個(gè)對(duì)象圖逐個(gè)序列化。基本數(shù)據(jù)類(lèi)型和數(shù)組也是可以通過(guò)他們序列化的。
serialVersionUID序列化版本號(hào)。
序列化運(yùn)行時(shí)使用了serialVersionUID與每個(gè)可序列化類(lèi)相關(guān)聯(lián),該序列號(hào)在反序列化過(guò)程中用于驗(yàn)證序列化對(duì)象的發(fā)送者和接收者是否為該對(duì)象加載了與序列化兼容的類(lèi)。為了保證序列化版本號(hào)的一致性,序列化類(lèi)需要聲明一個(gè)明確的版本號(hào)值。
private static final long serialVersionUID = 1L;
serialVersionUID字段只是一個(gè)標(biāo)識(shí)值。只用于當(dāng)前這個(gè)類(lèi)。
序列化機(jī)制:序列化分為兩大部分:序列化和反序列化。序列化是這個(gè)過(guò)程的第一部分,將數(shù)據(jù)分解成字節(jié)流,以便存儲(chǔ)在文件中或在網(wǎng)絡(luò)上傳輸。反序列化就是打開(kāi)字節(jié)流并重構(gòu)對(duì)象。對(duì)象序列化不僅要將基本數(shù)據(jù)類(lèi)型轉(zhuǎn)換成字節(jié) 表示,有時(shí)還要恢復(fù)數(shù)據(jù)。恢復(fù)數(shù)據(jù)要求有恢復(fù)數(shù)據(jù)的對(duì)象實(shí)例。ObjectOutputStream中的序列化過(guò)程與字節(jié)流連接,包括對(duì)象類(lèi)型和版本信 息。反序列化時(shí),JVM用頭信息生成對(duì)象實(shí)例,然后將對(duì)象字節(jié)流中的數(shù)據(jù)復(fù)制到對(duì)象數(shù)據(jù)成員中。
真正在需要序列化的時(shí)候都會(huì)根據(jù)實(shí)現(xiàn)的接口來(lái)進(jìn)行操作。
當(dāng)然也可以自己定義序列化的方法。
public interface Externalizable extends java.io.Serializable{}
一個(gè)類(lèi)如果要完全負(fù)責(zé)自己的序列化,就可以實(shí)現(xiàn)Externalizable接口,自己實(shí)現(xiàn)里面的兩個(gè)方法。利用這些方法可以控制對(duì)象數(shù)據(jù)成員如何寫(xiě)入字節(jié)流.類(lèi)實(shí)現(xiàn) Externalizable時(shí),頭寫(xiě)入對(duì)象流中,然后類(lèi)完全負(fù)責(zé)序列化和恢復(fù)數(shù)據(jù)成員,除了頭以外,根本沒(méi)有自動(dòng)序列化。這里要注意了。聲明類(lèi)實(shí)現(xiàn) Externalizable接口會(huì)有重大的安全風(fēng)險(xiǎn)。writeExternal()與readExternal()方法聲明為public,惡意類(lèi)可 以用這些方法讀取和寫(xiě)入對(duì)象數(shù)據(jù)。如果對(duì)象包含敏感信息,則要格外小心。
public class SerializerUtils {
/**
* 序列化
* @param object
* @return
*/
public static byte[] serialize(Object object) {
ObjectOutputStream objectOutputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
} catch (Exception e) {
return null;
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
//ignore
}
}
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
//ignore
}
}
}
}
/**
* 反序列化
*/
@SuppressWarnings("unchecked")
public static <T> T unserialize(byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
byteArrayInputStream = new ByteArrayInputStream(bytes);
objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
} catch (Exception e) {
return null;
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
//ignore
}
}
if (byteArrayInputStream != null) {
try {
byteArrayInputStream.close();
} catch (IOException e) {
//ignore
}
}
}
}
}
對(duì)象序列化成byte[]和byte[]轉(zhuǎn)換成對(duì)象方法。ObjectInputStream和ObjectOutputStream將對(duì)象從流中取出和寫(xiě)入到流中。這里使用的是ByteArrayInputStream和ByteArrayOutputStream作為容器然后?與byte[]進(jìn)行相互轉(zhuǎn)換。
在通過(guò)ObjectInputStream的readObject方法讀取到一個(gè)對(duì)象之后,這個(gè)對(duì)象是一個(gè)新的實(shí)例,但是其構(gòu)造方法是沒(méi)有被調(diào)用的,其中的?屬性的初始化代碼也沒(méi)有被執(zhí)行。對(duì)于那些沒(méi)有被序列化的屬性,在新創(chuàng)建出來(lái)的對(duì)象中的值都是默認(rèn)的。也就是說(shuō),這個(gè)對(duì)象從某種角度上來(lái)說(shuō)是不完備的。這有可能會(huì)造成一些隱含的錯(cuò)誤。調(diào)用者并不知道對(duì)象是通過(guò)一般的new操作符來(lái)創(chuàng)建的,還是通過(guò)反序列化所得到的。解決的辦法就是在類(lèi)的readObject方法里面,再執(zhí)行所需的對(duì)象初始化邏輯。對(duì)于一般的Java類(lèi)來(lái)說(shuō),構(gòu)造方法中包含了初始化的邏輯??梢园堰@些邏輯提取到一個(gè)方法中,在readObject方法中調(diào)用此方法。
如果序列化對(duì)象后,這個(gè)對(duì)象的類(lèi)發(fā)生了改變,這時(shí)需要注意是否需要有兼容性。一般來(lái)說(shuō),在新的版本中添加?xùn)|西不會(huì)產(chǎn)生什么問(wèn)題,而去掉一些屬性則是不行的。