摘要:在中,對象的序列化與反序列化被廣泛應用到遠程方法調用及網絡傳輸中。相關接口及類為了方便開發人員將對象進行序列化及反序列化提供了一套方便的來支持。未實現此接口的類將無法使其任何狀態序列化或反序列化。
序列化與反序列化
序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。一般將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩沖等。在網絡傳輸過程中,可以是字節或是XML等格式。而字節的或XML編碼格式可以還原完全相等的對象。這個相反的過程又稱為反序列化。
Java對象的序列化與反序列化
在Java中,我們可以通過多種方式來創建對象,并且只要對象沒有被回收我們都可以復用該對象。但是,我們創建出來的這些Java對象都是存在于JVM的堆內存中的。只有JVM處于運行狀態的時候,這些對象才可能存在。一旦JVM停止運行,這些對象的狀態也就隨之而丟失了。
但是在真實的應用場景中,我們需要將這些對象持久化下來,并且能夠在需要的時候把對象重新讀取出來。Java的對象序列化可以幫助我們實現該功能。
對象序列化機制(object serialization)是Java語言內建的一種對象持久化方式,通過對象序列化,可以把對象的狀態保存為字節數組,并且可以在有需要的時候將這個字節數組通過反序列化的方式再轉換成對象。對象序列化可以很容易的在JVM中的活動對象和字節數組(流)之間進行轉換。
在Java中,對象的序列化與反序列化被廣泛應用到RMI(遠程方法調用)及網絡傳輸中。
相關接口及類
Java為了方便開發人員將Java對象進行序列化及反序列化提供了一套方便的API來支持。其中包括以下接口和類:
java.io.Serializable java.io.Externalizable ObjectOutput ObjectInput ObjectOutputStream ObjectInputStream
Serializable 接口
類通過實現 java.io.Serializable 接口以啟用其序列化功能。未實現此接口的類將無法使其任何狀態序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于標識可序列化的語義。 (該接口并沒有方法和字段,為什么只有實現了該接口的類的對象才能被序列化呢?)
當試圖對一個對象進行序列化的時候,如果遇到不支持 Serializable 接口的對象。在此情況下,將拋出NotSerializableException。
如果要序列化的類有父類,要想同時將在父類中定義過的變量持久化下來,那么父類也應該集成java.io.Serializable接口。
下面是一個實現了java.io.Serializable接口的類
package com.hollischaung.serialization.SerializableDemos; import java.io.Serializable; /** ?* Created by hollis on 16/2/17. ?* 實現Serializable接口 ?*/ public class User1 implements Serializable { ? ? ? private String name; ? ? private int 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; ? ? } ? ? ? @Override ? ? public String toString() { ? ? ? ? return "User{" + ? ? ? ? ? ? ? ? "name="" + name + """ + ? ? ? ? ? ? ? ? ", age=" + age + ? ? ? ? ? ? ? ? "}"; ? ? } }
通過下面的代碼進行序列化及反序列化
package com.hollischaung.serialization.SerializableDemos; ? import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; ? import java.io.*; /** ?* Created by hollis on 16/2/17. ?* SerializableDemo1 結合SerializableDemo2說明 一個類要想被序列化必須實現Serializable接口 ?*/ public class SerializableDemo1 { ? ? ? public static void main(String[] args) { ? ? ? ? //Initializes The Object ? ? ? ? User1 user = new User1(); ? ? ? ? user.setName("hollis"); ? ? ? ? user.setAge(23); ? ? ? ? System.out.println(user); ? ? ? ? ? //Write Obj to File ? ? ? ? ObjectOutputStream oos = null; ? ? ? ? try { ? ? ? ? ? ? oos = new ObjectOutputStream(new FileOutputStream("tempFile")); ? ? ? ? ? ? oos.writeObject(user); ? ? ? ? } catch (IOException e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } finally { ? ? ? ? ? ? IOUtils.closeQuietly(oos); ? ? ? ? } ? ? ? ? ? //Read Obj from File ? ? ? ? File file = new File("tempFile"); ? ? ? ? ObjectInputStream ois = null; ? ? ? ? try { ? ? ? ? ? ? ois = new ObjectInputStream(new FileInputStream(file)); ? ? ? ? ? ? User1 newUser = (User1) ois.readObject(); ? ? ? ? ? ? System.out.println(newUser); ? ? ? ? } catch (IOException e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } catch (ClassNotFoundException e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } finally { ? ? ? ? ? ? IOUtils.closeQuietly(ois); ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? FileUtils.forceDelete(file); ? ? ? ? ? ? } catch (IOException e) { ? ? ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? ? ? } ? ? ? ? } ? ? ? } } ? //OutPut: //User{name="hollis", age=23} //User{name="hollis", age=23}
更多關于Serializable的使用,請參考代碼實例
Externalizable接口
除了Serializable 之外,java中還提供了另一個序列化接口Externalizable
為了了解Externalizable接口和Serializable接口的區別,先來看代碼,我們把上面的代碼改成使用Externalizable的形式。
package com.hollischaung.serialization.ExternalizableDemos; ? import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; ? /** ?* Created by hollis on 16/2/17. ?* 實現Externalizable接口 ?*/ public class User1 implements Externalizable { ? ? ? private String name; ? ? private int 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 void writeExternal(ObjectOutput out) throws IOException { ? ? ? } ? ? ? public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { ? ? ? } ? ? ? @Override ? ? public String toString() { ? ? ? ? return "User{" + ? ? ? ? ? ? ? ? "name="" + name + """ + ? ? ? ? ? ? ? ? ", age=" + age + ? ? ? ? ? ? ? ? "}"; ? ? } } package com.hollischaung.serialization.ExternalizableDemos; ? import java.io.*; ? /** ?* Created by hollis on 16/2/17. ?*/ public class ExternalizableDemo1 {
?
? ? //為了便于理解和節省篇幅,忽略關閉流操作及刪除文件操作。真正編碼時千萬不要忘記
? ? //IOException直接拋出
? ? public static void main(String[] args) throws IOException, ClassNotFoundException {
? ? ? ? //Write Obj to file
? ? ? ? ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
? ? ? ? User1 user = new User1();
? ? ? ? user.setName("hollis");
? ? ? ? user.setAge(23);
? ? ? ? oos.writeObject(user);
? ? ? ? //Read Obj from file
? ? ? ? File file = new File("tempFile");
? ? ? ? ObjectInputStream ois = ?new ObjectInputStream(new FileInputStream(file));
? ? ? ? User1 newInstance = (User1) ois.readObject();
? ? ? ? //output
? ? ? ? System.out.println(newInstance);
? ? }
}
//OutPut:
//User{name="null", age=0}
通過上面的實例可以發現,對User1類進行序列化及反序列化之后得到的對象的所有屬性的值都變成了默認值。也就是說,之前的那個對象的狀態并沒有被持久化下來。這就是Externalizable接口和Serializable接口的區別:
Externalizable繼承了Serializable,該接口中定義了兩個抽象方法:writeExternal()與readExternal()。當使用Externalizable接口來進行序列化與反序列化的時候需要開發人員重寫writeExternal()與readExternal()方法。由于上面的代碼中,并沒有在這兩個方法中定義序列化實現細節,所以輸出的內容為空。還有一點值得注意:在使用Externalizable進行序列化的時候,在讀取對象時,會調用被序列化類的無參構造器去創建一個新的對象,然后再將被保存對象的字段的值分別填充到新對象中。所以,實現Externalizable接口的類必須要提供一個public的無參的構造器。
按照要求修改之后代碼如下:
package com.hollischaung.serialization.ExternalizableDemos; ? import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; ? /** ?* Created by hollis on 16/2/17.
?* 實現Externalizable接口,并實現writeExternal和readExternal方法
?
*/ public class User2 implements Externalizable { ? ? ? private String name; ? ? private int 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 void writeExternal(ObjectOutput out) throws IOException { ? ? ? ? out.writeObject(name); ? ? ? ? out.writeInt(age); ? ? } ? ? ? public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { ? ? ? ? name = (String) in.readObject(); ? ? ? ? age = in.readInt(); ? ? } ? ? ? @Override ? ? public String toString() { ? ? ? ? return "User{" + ? ? ? ? ? ? ? ? "name="" + name + """ + ? ? ? ? ? ? ? ? ", age=" + age + ? ? ? ? ? ? ? ? "}"; ? ? } } ? package com.hollischaung.serialization.ExternalizableDemos; ? import java.io.*; ? /** ?* Created by hollis on 16/2/17. ?*/ public class ExternalizableDemo2 {
?
? ? //為了便于理解和節省篇幅,忽略關閉流操作及刪除文件操作。真正編碼時千萬不要忘記
? ? //IOException直接拋出
?
? public static void main(String[] args) throws IOException, ClassNotFoundException {
? ? ? ? //Write Obj to file
? ? ? ? ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
? ? ? ? User2 user = new User2();
? ? ? ? user.setName("hollis");
? ? ? ? user.setAge(23);
? ? ? ? oos.writeObject(user);
? ? ? ? //Read Obj from file
? ? ? ? File file = new File("tempFile");
? ? ? ? ObjectInputStream ois = ?new ObjectInputStream(new FileInputStream(file));
? ? ? ? User2 newInstance = (User2) ois.readObject();
? ? ? ? //output
? ? ? ? System.out.println(newInstance);
? ? }
}
//OutPut:
//User{name="hollis", age=23}
這次,就可以把之前的對象狀態持久化下來了。
如果User類中沒有無參數的構造函數,在運行時會拋出異常:java.io.InvalidClassException
更多Externalizable接口使用實例請參考代碼實例
ObjectOutput和ObjectInput 接口
ObjectInput接口 擴展自 DataInput 接口以包含對象的讀操作。
DataInput 接口用于從二進制流中讀取字節,并根據所有 Java 基本類型數據進行重構。同時還提供根據 UTF-8 修改版格式的數據重構 String 的工具。
對于此接口中的所有數據讀取例程來說,如果在讀取所需字節數之前已經到達文件末尾 (end of file),則將拋出 EOFException(IOException 的一種)。如果因為到達文件末尾以外的其他原因無法讀取字節,則將拋出 IOException 而不是 EOFException。尤其是,在輸入流已關閉的情況下,將拋出 IOException。
ObjectOutput 擴展 DataOutput 接口以包含對象的寫入操作。
DataOutput 接口用于將數據從任意 Java 基本類型轉換為一系列字節,并將這些字節寫入二進制流。同時還提供了一個將 String 轉換成 UTF-8 修改版格式并寫入所得到的系列字節的工具。
對于此接口中寫入字節的所有方法,如果由于某種原因無法寫入某個字節,則拋出 IOException。
ObjectOutputStream類和ObjectInputStream類
通過前面的代碼片段中我們也能知道,我們一般使用ObjectOutputStream的writeObject方法把一個對象進行持久化。再使用ObjectInputStream的readObject從持久化存儲中把對象讀取出來。
更多關于ObjectInputStream和ObjectOutputStream的相關知識歡迎閱讀我的另外兩篇博文:深入分析Java的序列化與反序列化、單例與序列化的那些事兒
Transient 關鍵字
Transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設為初始值,如 int 型的是 0,對象型的是 null。關于Transient 關鍵字的拓展知識歡迎閱讀深入分析Java的序列化與反序列化
序列化ID
虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
序列化 ID 在 Eclipse 下提供了兩種生成策略,一個是固定的 1L,一個是隨機生成一個不重復的 long 類型數據(實際上是使用 JDK 工具生成),在這里有一個建議,如果沒有特殊需求,就是用默認的 1L 就可以,這樣可以確保代碼一致時反序列化成功。那么隨機生成的序列化 ID 有什么作用呢,有些時候,通過改變序列化 ID 可以用來限制某些用戶的使用。
參考資料
維基百科
理解Java對象序列化
Java 序列化的高級認識
關注“動力節點Java學院”微信公眾號,獲取更多相關資訊,現在報名Java培訓,可免費參加Java初級課程,親身體驗這里的學習氛圍。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/66742.html
摘要:序列化工具類序列化工具的序列化與反序列化使用實現序列化和反序列化反序列化時,必須要有默認構造函數,否則報錯使用序列化緩存此類分別包含序列化序列化序列化三種序列化方式。 序列化工具類 序列化即將對象序列化為字節數組,反序列化就是將字節數組恢復成對象。主要的目的是方便傳輸和存儲。 序列化工具類: public class SerializeUtil { private stati...
摘要:一序列化和反序列化的概念把對象轉換為字節序列的過程稱為對象的序列化把字節序列恢復為對象的過程稱為對象的反序列化。代表對象輸入流,它的方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回。 一、序列化和反序列化的概念 把對象轉換為字節序列的過程稱為對象的序列化;把字節序列恢復為對象的過程稱為對象的反序列化。 對象的序列化主要有兩種用途: 1) 把對象的字節序列永久地保...
摘要:回到的第二方法的用法,通過上面的分析,我們可以知道,方法其實也是用來獲取泛型的實際類型的,這樣就可以將響應反序列化為帶泛型的類型了。在很多反序列化的開源組件中,都用了這個原理例如的方法,所以我們會經常見到實例化的時候會多個花括號。 前段日子在使用google-http-client.jar 這個組件做http請求時,發現一件有趣的事情,具體代碼如下: try { ...
摘要:集合的特點集合的特點類介紹類表示了一個持久的屬性集。可保存在流中或從流中加載。屬性列表中每個鍵及其對應值都是一個字符串特點的子類,集合中的方法都可以用。該集合沒有泛型。鍵值可以存儲到集合中,也可以存儲到持久化的設備硬盤盤光盤上。 01Properties集合的特點 * A: Properties集合的特點 * a: Properties類介紹 * Propert...
摘要:導讀閱讀本文需要有足夠的時間,筆者會由淺到深帶你一步一步了解一個資深架構師所要掌握的各類知識點,你也可以按照文章中所列的知識體系對比自身,對自己進行查漏補缺,覺得本文對你有幫助的話,可以點贊關注一下。目錄一基礎篇二進階篇三高級篇四架構篇五擴 導讀:閱讀本文需要有足夠的時間,筆者會由淺到深帶你一步一步了解一個資深架構師所要掌握的各類知識點,你也可以按照文章中所列的知識體系對比自身,對自己...
閱讀 3780·2021-08-30 09:47
閱讀 3710·2019-08-30 15:56
閱讀 682·2019-08-30 14:18
閱讀 703·2019-08-29 16:17
閱讀 2070·2019-08-29 11:07
閱讀 648·2019-08-26 13:53
閱讀 3452·2019-08-26 10:26
閱讀 2499·2019-08-23 18:30