詳解Java序列化機(jī)制
在程序中為了能直接以 Java 對(duì)象的形式進(jìn)行保存,然后再重新得到該 Java 對(duì)象,這就需要序列化能力。序列化其實(shí)可以看成是一種機(jī)制,按照一定的格式將 Java 對(duì)象的某狀態(tài)轉(zhuǎn)成介質(zhì)可接受的形式,以方便存儲(chǔ)或傳輸。其實(shí)想想就大致清楚基本流程,序列化時(shí)將 Java 對(duì)象相關(guān)的類信息、屬性及屬性值等等保存起來(lái),反序列化時(shí)再根據(jù)這些信息構(gòu)建出 Java 對(duì)象。而過(guò)程可能涉及到其他對(duì)象的引用,所以這里引用的對(duì)象的相關(guān)信息也要參與序列化。Java 中進(jìn)行序列化操作需要實(shí)現(xiàn) Serializable 或 Externalizable 接口。
序列化的作用 提供一種簡(jiǎn)單又可擴(kuò)展的對(duì)象保存恢復(fù)機(jī)制。 對(duì)于遠(yuǎn)程調(diào)用,能方便對(duì)對(duì)象進(jìn)行編碼和解碼,就像實(shí)現(xiàn)對(duì)象直接傳輸。 可以將對(duì)象持久化到介質(zhì)中,就像實(shí)現(xiàn)對(duì)象直接存儲(chǔ)。 允許對(duì)象自定義外部存儲(chǔ)的格式。 序列化例子FileOutputStream f = new FileOutputStream('tmp.o'); ObjectOutput s = new ObjectOutputStream(f); s.writeObject('test'); s.writeObject(new ArrayList()); s.flush();
常見(jiàn)的使用方式是直接將對(duì)象寫(xiě)入流中,比如上述例子中,創(chuàng)建了 FileOutputStream 對(duì)象,其對(duì)應(yīng)輸出到 tmp.o 文件中,然后創(chuàng)建 ObjectOutputStream 對(duì)象嵌套前面的輸出流。當(dāng)我們調(diào)用 writeObject 方法時(shí)即能進(jìn)行序列化操作。writeObject 方法這里需要說(shuō)明下,在對(duì)某個(gè)對(duì)象進(jìn)行寫(xiě)入時(shí),它其實(shí)不僅僅序列化自己,還會(huì)去遍歷尋找相關(guān)引用的其他對(duì)象,由自己和其他引用對(duì)象組成的一個(gè)完整的對(duì)象圖關(guān)系都會(huì)被序列化。對(duì)于數(shù)組、enum、Class類對(duì)象、ObjectStreamClass 和 String 等都會(huì)做特殊處理,而其他對(duì)象序列化則需要實(shí)現(xiàn) Serializable 或 Externalizable 接口。
反序列化例子FileInputStream in = new FileInputStream('tmp.o'); ObjectInputStream s = new ObjectInputStream(in); String test = (String)s.readObject(); List list = (ArrayList)s.readObject();
針對(duì)序列化則存在反序列化操作,通過(guò)流直接讀取對(duì)象,先創(chuàng)建 FileInputStream 對(duì)象,其對(duì)應(yīng)輸入文件為 tmp.o,然后創(chuàng)建 ObjectInputStream 對(duì)象嵌套前面的輸入流,接著則可以調(diào)用 readObject 方法讀取對(duì)象。其中調(diào)用 readObject 方法反序列操作的過(guò)程,除了會(huì)恢復(fù)對(duì)象自己之外還會(huì)遍歷整個(gè)完整的對(duì)象圖,創(chuàng)建整個(gè)對(duì)象圖包含的所有對(duì)象。
serialVersionUID 有什么用在序列化操作時(shí),經(jīng)常會(huì)看到實(shí)現(xiàn)了 Serializable 接口的類會(huì)存在一個(gè) serialVersionUID 屬性,并且它是一個(gè)固定數(shù)值的靜態(tài)變量。比如如下,這個(gè)屬性有什么作用?其實(shí)它主要用于驗(yàn)證版本一致性,每個(gè)類都擁有這么一個(gè) ID,在序列化的時(shí)候會(huì)一起被寫(xiě)入流中,那么在反序列化的時(shí)候就被拿出來(lái)跟當(dāng)前類的 serialVersionUID 值進(jìn)行比較,兩者相同則說(shuō)明版本一致,可以序列化成功,而如果不同則序列化失敗。
private static final long serialVersionUID = -6849794470754667710L;
一般情況下我們可以自己定義 serialVersionUID 的值或者 IDE 幫我們自動(dòng)生成,而如果我們不顯示定義 serialVersionUID 的話,這不代表不存在 serialVersionUID,而是由 JDK 幫我們生成,生成規(guī)則是會(huì)利用類名、類修飾符、接口名、字段、靜態(tài)初始化信息、構(gòu)造函數(shù)信息、方法名、方法修飾符、方法簽名等組成的信息,經(jīng)過(guò) SHA 算法生成摘要即是最終的 serialVersionUID 值。
父類序列化什么情況如果一個(gè)子類實(shí)現(xiàn)了 Serializable 接口而父類沒(méi)有實(shí)現(xiàn)該接口,則在序列化子類時(shí),子類的屬性狀態(tài)會(huì)被寫(xiě)入而父類的屬性狀態(tài)將不被寫(xiě)入。所以如果想要父類屬性狀態(tài)也一起參與序列化,就要讓它也實(shí)現(xiàn) Serializable 接口。另外,如果父類未實(shí)現(xiàn) Serializable 接口則反序列化生成的對(duì)象會(huì)再次調(diào)用父類的構(gòu)造函數(shù),以此完成對(duì)父類的初始化。所以父類屬性初始值一般都是類型的默認(rèn)值。比如下面,F(xiàn)ather 類的屬性不會(huì)參與序列化,反序列化時(shí) Father 對(duì)象的屬性的值為默認(rèn)值0。
public class Father {public int f; public Father() {} } public class Son extends Father implements Serializable {public int s; public Son() { super();} }哪些字段會(huì)序列化
在序列化時(shí)類的哪些字段會(huì)參與到序列化中呢?其實(shí)有兩種方式?jīng)Q定哪些字段會(huì)被序列化,1. 默認(rèn)方式,Java對(duì)象中的非靜態(tài)和非transient的字段都會(huì)被定義為需要序列的字段。2. 另外一種方式是通過(guò) ObjectStreamField 數(shù)組來(lái)聲明類需要序列化的對(duì)象。可以看到普通的字段都是默認(rèn)會(huì)被序列化的,而對(duì)于某些包含敏感信息的字段我們不希望它參與序列化,那么最簡(jiǎn)單的方式就是可以將該字段聲明為 transient。如何使用 ObjectStreamField?舉個(gè)例子,如下,A類中有 name 和 password 兩個(gè)字段,通過(guò) ObjectStreamField 數(shù)組聲明只序列化 name 字段。這種聲明的方式不用糾結(jié)為什么這樣,這僅僅是約定了這樣而已。
public class A implements Serializable { String name; String passwordprivate static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField('name', String.class)}; }枚舉類型的序列化
Enum 類型的序列化與普通的 Java 類的序列化有所不同,那么在深入之前可以先看這篇文章深入了解下枚舉,《 [從JDK角度認(rèn)識(shí)枚舉enum][JDK_enum]》。所以我們知道枚舉被編譯后會(huì)變成一個(gè)繼承 java.lang.Enum 的類,而且枚舉里面的元素被聲明成 static final ,另外生成一個(gè)靜態(tài)代碼塊 static{},最后還會(huì)生成 values 和 valueOf 兩個(gè)方法。Enum 類是一個(gè)抽象類,主要有 name 和 ordinal 兩個(gè)屬性,分別用于表示枚舉元素的名稱和枚舉元素的位置索引。Enum 類型參與序列化時(shí)只會(huì)將枚舉對(duì)象中的 name 屬性寫(xiě)入,而其他的屬性則不參與進(jìn)來(lái)。在反序列化時(shí),則是先讀取 name 屬性,然后再通過(guò) java.lang.Enum 類的 valueOf 方法找到對(duì)應(yīng)的枚舉類型。除此之外,不能自定義 Enum 類型的序列化,所以 writeObject, readObject, readObjectNoData, writeReplace 以及 readResolve 等方法在序列化時(shí)會(huì)被忽略,類似的,serialPersistentFields 和 serialVersionUID 屬性都會(huì)被忽略。最后,在序列化場(chǎng)景中,涉及到使用枚舉的情況時(shí)要仔細(xì)設(shè)計(jì)好,不然很可能會(huì)因?yàn)楹竺嫔?jí)修改了枚舉類的結(jié)構(gòu)而導(dǎo)致反序列化失敗。
Externalizable 接口作用Externalizable 接口主要就是提供給用戶自己控制序列化內(nèi)容,雖然前面我們也看到了 transient 和 ObjectStreamField 能定義序列化的字段,但通過(guò) Externalizable 接口則能更加靈活。可以看到它其實(shí)繼承了 Serializable 接口,提供了 writeExternal 和 readExternal 兩個(gè)方法,也就是在這兩個(gè)方法內(nèi)控制序列化和反序列化的內(nèi)容。
public interface Externalizable extends java.io.Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
比如下面的例子,我們可以在 writeExternal 方法中額外寫(xiě)入 Date 對(duì)象,然后再寫(xiě)入 value 值。對(duì)應(yīng)的,反序列化時(shí)則是在 readExternal 方法中讀取 Date 對(duì)象和 value。這樣就完成了自定義序列化操作。
public class ExternalizableTest implements Externalizable {public String value = 'test'; public ExternalizableTest() {} public void writeExternal(ObjectOutput out) throws IOException { Date d = new Date(); out.writeObject(d); out.writeObject(value);} public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { Date d = (Date) in.readObject(); System.out.println(d); System.out.println((String) in.readObject());} }寫(xiě)入時(shí)替換對(duì)象
正常情況下序列化某個(gè)對(duì)象時(shí)寫(xiě)入的正是當(dāng)前的對(duì)象,但如果說(shuō)我們要替換當(dāng)前的對(duì)象而寫(xiě)入其他對(duì)象的話則可以通過(guò) writeReplace 方法來(lái)實(shí)現(xiàn)。比如下面,person 類通過(guò) writeReplace 方法最終可以寫(xiě)入 Object 數(shù)組對(duì)象。所以我們?cè)诜葱蛄谢瘯r(shí)就不再是轉(zhuǎn)換成 Person 類型,而是要轉(zhuǎn)換為 Object 數(shù)組對(duì)象。
class Person implements Serializable {private String name;private int age; public Person(String name, int age) { this.name = name; this.age = age;} private Object writeReplace() throws ObjectStreamException { Object[] properties = new Object[2]; properties[0] = name; properties[1] = age; return properties;} }
ObjectInputStream ois = new ObjectInputStream(new FileInputStream('test.o')); Object[] properties = (Object[]) ois.readObject();讀取時(shí)替換對(duì)象
上面介紹了在寫(xiě)入時(shí)可以替換對(duì)象,而在讀取時(shí)也同樣支持替換對(duì)象的,它是通過(guò) readResolve 方法實(shí)現(xiàn)的。比如下面,在 readResolve 方法返回 2222,則反序列化讀取時(shí)不再是 Person 對(duì)象,而是 2222。
class Person implements Serializable {private String name;private int age; public Person(String name, int age) { this.name = name; this.age = age;} private Object readResolve() throws ObjectStreamException { return 2222;} }
ObjectInputStream ois = new ObjectInputStream(new FileInputStream('test.o')); Object o = ois.readObject();
以上就是詳解Java序列化機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于Java序列化機(jī)制的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. Nginx+php配置文件及原理解析2. Yii2.0引入CSS,JS文件方法3. JSP數(shù)據(jù)交互實(shí)現(xiàn)過(guò)程解析4. 解決啟動(dòng)django,瀏覽器顯示“服務(wù)器拒絕訪問(wèn)”的問(wèn)題5. vue使用webSocket更新實(shí)時(shí)天氣的方法6. python virtualenv和flask安裝沒(méi)有名為flask的模塊7. 討論CSS中的各類居中方式8. 關(guān)于HTML5的img標(biāo)簽9. python 生成任意形狀的凸包圖代碼10. ASP.NET MVC獲取多級(jí)類別組合下的產(chǎn)品
