摘要:對象序列化對象序列化機制允許把內存中的對象轉換成與平臺無關的二進制流,從而可以保存到磁盤或者進行網絡傳輸,其它程序獲得這個二進制流后可以將其恢復成原來的對象。
對象序列化
對象序列化機制允許把內存中的Java對象轉換成與平臺無關的二進制流,從而可以保存到磁盤或者進行網絡傳輸,其它程序獲得這個二進制流后可以將其恢復成原來的Java對象。 序列化機制可以使對象可以脫離程序的運行而對立存在
序列化的含義和意義 序列化序列化機制可以使對象可以脫離程序的運行而對立存在
序列化(Serialize)指將一個java對象寫入IO流中,與此對應的是,對象的反序列化(Deserialize)則指從IO流中恢復該java對象
如果需要讓某個對象可以支持序列化機制,必須讓它的類是可序列化(serializable),為了讓某個類可序列化的,必須實現如下兩個接口之一:
Serializable:標記接口,實現該接口無須實現任何方法,只是表明該類的實例是可序列化的
Externalizable
所有在網絡上傳輸的對象都應該是可序列化的,否則將會出現異常;所有需要保存到磁盤里的對象的類都必須可序列化;程序創建的每個JavaBean類都實現Serializable;
使用對象流實現序列化實現Serializable實現序列化的類,程序可以通過如下兩個步驟來序列化該對象:
1.創建一個ObjectOutputStream,這個輸出流是一個處理流,所以必須建立在其他節點流的基礎之上
// 創建個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
2.調用ObjectOutputStream對象的writeObject方法輸出可序列化對象
// 將一個Person對象輸出到輸出流中 oos.writeObject(per);
定義一個NbaPlayer類,實現Serializable接口,該接口標識該類的對象是可序列化的
public class NbaPlayer implements java.io.Serializable { private String name; private int number; // 注意此處沒有提供無參數的構造器! public NbaPlayer(String name, int number) { System.out.println("有參數的構造器"); this.name = name; this.number = number; } // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // number的setter和getter方法 public void setNumber(int number) { this.number = number; } public int getNumber() { return this.number; } }
使用ObjectOutputStream將一個NbaPlayer對象寫入磁盤文件
import java.io.*; public class WriteObject { public static void main(String[] args) { try( // 創建一個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("object.txt"))) { NbaPlayer player = new NbaPlayer("維斯布魯克", 0); // 將player對象寫入輸出流 oos.writeObject(player); } catch (IOException ex) { ex.printStackTrace(); } } }反序列化
從二進制流中恢復Java對象,則需要使用反序列化,程序可以通過如下兩個步驟來序列化該對象:
1.創建一個ObjectInputStream輸入流,這個輸入流是一個處理流,所以必須建立在其他節點流的基礎之上
// 創建個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
2.調用ObjectInputStream對象的readObject()方法讀取流中的對象,該方法返回一個Object類型的Java對象,可進行強制類型轉換成其真實的類型
// 從輸入流中讀取一個Java對象,并將其強制類型轉換為Person類 Person p = (Person)ois.readObject();
從object.txt文件中讀取NbaPlayer對象的步驟
import java.io.*; public class ReadObject { public static void main(String[] args) { try( // 創建一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("object.txt"))) { // 從輸入流中讀取一個Java對象,并將其強制類型轉換為NbaPlayer類 NbaPlayer player = (NbaPlayer)ois.readObject(); System.out.println("名字為:" + player.getName() + " 號碼為:" + player.getNumber()); } catch (Exception ex) { ex.printStackTrace(); } } }
反序列化讀取的僅僅是Java對象的數據,而不是Java類,因此采用反序列化恢復Java對象時,必須提供Java對象所屬的class文件,否則會引發ClassNotFoundException異常;反序列化機制無須通過構造器來初始化Java對象
如果使用序列化機制向文件中寫入了多個Java對象,使用反序列化機制恢復對象必須按照實際寫入的順序讀取。當一個可序列化類有多個父類時(包括直接父類和間接父類),這些父類要么有無參的構造器,要么也是可序列化的—否則反序列化將拋出InvalidClassException異常。如果父類是不可序列化的,只是帶有無參數的構造器,則該父類定義的Field值不會被序列化到二進制流中
對象引用的序列化如果某個類的Field類型不是基本類型或者String類型,而是另一個引用類型,那么這個引用類型必須是可序列化的,否則有用該類型的Field的類也是不可序列化的
public class AllStar implements java.io.Serializable { private String name; private NbaPlayer player; public AllStar(String name, NbaPlayer player) { this.name = name; this.player = player; } // name的setter和getter方法 public String getName() { return this.name; } public void setName(String name) { this.name = name; } // player的setter和getter方法 public NbaPlayer getPlayer() { return player; } public void setPlayer(NbaPlayer player) { this.player = player; } }Java特殊的序列化算法
所有保存到磁盤中的對象都有一個序列化編號
當程序試圖序列化一個對象時,程序將先檢查該對象是否已經被序列化過,只有該對象從未(在本次虛擬中機)被序列化過,系統才會將該對象轉換成字節序列并輸出
如果某個對象已經序列化過,程序將只是直接輸出一個序列化編號,而不是再次重新序列化該對象
import java.io.*; public class WriteAllStar { public static void main(String[] args) { try( // 創建一個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("allStar.txt"))) { NbaPlayer player = new NbaPlayer("詹姆斯哈登", 13); AllStar allStar1 = new AllStar("西部全明星", player); AllStar allStar2 = new AllStar("首發后衛", player); // 依次將四個對象寫入輸出流 oos.writeObject(allStar1); oos.writeObject(allStar2); oos.writeObject(player); oos.writeObject(allStar2); } catch (IOException ex) { ex.printStackTrace(); } } }
4個寫入輸出流的對象,實際上只序列化了3個,而且序列的兩個AllStar對象的player引用實際是同一個NbaPlayer對象。以下程序讀取序列化文件中的對象
import java.io.*; public class ReadAllStar { public static void main(String[] args) { try( // 創建一個ObjectInputStream輸出流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("allStar.txt"))) { // 依次讀取ObjectInputStream輸入流中的四個對象 AllStar star1 = (AllStar)ois.readObject(); AllStar star2 = (AllStar)ois.readObject(); NbaPlayer player = (NbaPlayer)ois.readObject(); AllStar star3 = (AllStar)ois.readObject(); // 輸出true System.out.println("star1的player引用和player是否相同:" + (star1.getPlayer() == player)); // 輸出true System.out.println("star2的player引用和player是否相同:" + (star2.getPlayer() == player)); // 輸出true System.out.println("star2和star3是否是同一個對象:" + (star2 == star3)); } catch (Exception ex) { ex.printStackTrace(); } } }
如果多次序列化同一個可變Java對象時,只有第一次序列化時才會把該Java對象轉換成字節序列并輸出
當使用Java序列化機制序列化可變對象時,只有第一次調用WriteObject()方法來輸出對象時才會將對象轉換成字節序列,并寫入到ObjectOutputStream;即使在后面程序中,該對象的實例變量發生了改變,再次調用WriteObject()方法輸出該對象時,改變后的實例變量也不會被輸出
import java.io.*; public class SerializeMutable { public static void main(String[] args) { try( // 創建一個ObjectOutputStream輸入流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("mutable.txt")); // 創建一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("mutable.txt"))) { NbaPlayer player = new NbaPlayer("斯蒂芬庫里", 30); // 系統會player對象轉換字節序列并輸出 oos.writeObject(player); // 改變per對象的name實例變量 player.setName("塞斯庫里"); // 系統只是輸出序列化編號,所以改變后的name不會被序列化 oos.writeObject(player); NbaPlayer player1 = (NbaPlayer)ois.readObject(); //① NbaPlayer player2 = (NbaPlayer)ois.readObject(); //② // 下面輸出true,即反序列化后player1等于player2 System.out.println(player1 == player2); // 下面依然看到輸出"斯蒂芬庫里",即改變后的實例變量沒有被序列化 System.out.println(player2.getName()); } catch (Exception ex) { ex.printStackTrace(); } } }自定義序列化
在一些特殊的場景下,如果一個類里包含的某些實例變量是敏感信息,這時不希望系統將該實例變量值進行實例化;或者某個實例變量的類型是不可序列化的,因此不希望對該實例變量進行遞歸實例化,以避免引發java.io.NotSerializableException異常
當對某個對象進行序列化時,系統會自動把該對象的所有實例變量依次進行序列化,如果某個實例變量引用到另一個對象,則被引用的對象也會被序列化;如果被引用的對象的實例變量也引用了其他對象,則被引用的對象也會被序列化,這種情況被稱為遞歸序列化
在實例變量前面使用transient關鍵字修飾,可以指定java序列化時無須理會該實例變量
public class NbaPlayer implements java.io.Serializable { private String name; private transient int number; // 注意此處沒有提供無參數的構造器! public NbaPlayer(String name, int number) { System.out.println("有參數的構造器"); this.name = name; this.number = number; } // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // number的setter和getter方法 public void setNumber(int number) { this.number = number; } public int getNumber() { return this.number; } }
transient關鍵字只能用于修飾實例變量,不可修飾Java程序中的其他成分
import java.io.*; public class TransientTest { public static void main(String[] args) { try( // 創建一個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("transient.txt")); // 創建一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("transient.txt"))) { NbaPlayer per = new NbaPlayer("克萊湯普森", 11); // 系統會per對象轉換字節序列并輸出 oos.writeObject(per); NbaPlayer p = (NbaPlayer)ois.readObject(); System.out.println(p.getNumber()); } catch (Exception ex) { ex.printStackTrace(); } } }
在序列化和反序列化過程中需要特殊處理的類應該提供如下特殊簽名的方法,這些特殊的方法用以實現自定義:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
private void readObjectNoData() throws ObjectStreamException
writeObject()方法負責寫入特定類的實例的狀態,以便相應的readObject()方法可以恢復它。通過重寫該方法,可以完全獲得對序列化機制的控制,自主決定哪些實例變量需要序列化,怎樣序列化。在默認情況下,該方法會調用out.defaultWriteObject來保存Java對象的各實例變量,從而可以實現序列化Java對象狀態的目的
readObject()方法負責從流中讀取并恢復對象實例變量,通過重寫該方法,可以完全獲得對反序列化機制的控制,可以自主決定需要反序列化哪些實例變量,怎樣反序列化。在默認情況下,該方法會調用in.defaultReadObject來恢復Java對象的非瞬態實例變量
通常情況下readObject()方法與writeObject()方法對應,如果writeObject()方法中對Java對象的實例變量進行了一些處理,則應該在readObject()方法中對該實例變量進行相應的反處理,以便正確恢復該對象
當序列化流不完整時,readObjectNoData()方法可以用來正確地初始化反序列化的對象
import java.io.IOException; public class NbaPlayer implements java.io.Serializable { private String name; private int number; // 注意此處沒有提供無參數的構造器! public NbaPlayer(String name, int number) { System.out.println("有參數的構造器"); this.name = name; this.number = number; } // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // number的setter和getter方法 public void setNumber(int number) { this.number = number; } public int getNumber() { return this.number; } private void writeObject(java.io.ObjectOutputStream out) throws IOException { // 將name實例變量值反轉后寫入二進制流 out.writeObject(new StringBuffer(name).reverse()); out.writeInt(number); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { // 將讀取的字符串反轉后賦給name實例變量 this.name = ((StringBuffer)in.readObject()).reverse().toString(); this.number = in.readInt(); } }
writeObject()方法存儲實例變量的順序應該和readObject()方法中恢復實例變量的順序一致,否則將不能正常恢復該Java對象
ANY-ACCESS-MODIFIER Object writeReplace() 實現序列化某個對象時替換該對象
此writeReplace()方法將由序列化機制調用,只要該方法存在。因為該方法可以擁有私有(private),受保護的(protected)和包私有(package-private)等訪問權限,所以其子類有可能獲得該方法
下面程序的writeReplace()方法,這樣可以在寫入NbaPlayer對象時將該對象替換成ArrayList
// 重寫writeReplace方法,程序在序列化該對象之前,先調用該方法 private Object writeReplace() throws ObjectStreamException { ArrayList
Java的序列化機制保證在序列化某個對象之前,先調用該對象的writeReplace()方法,如果該方法返回另一個Java對象,則系統轉為序列化另一個對象。如下程序表面上是序列化NbaPlayer對象,但實際上序列化的是ArrayList
// 系統將player對象轉換字節序列并輸出 oos.writeObject(player); // 反序列化讀取得到的是ArrayList ArrayList list = (ArrayList)ois.readObject(); System.out.println(list);
系統在序列化某個對象之前,會先調用該對象的writeReplace()和writeObject()兩個方法,系統總是先調用被序列化對象的writeReplace()方法,如果該方法返回另一個對象,系統將再次調用另一個對象的writeReplace()方法,直到該方法不再返回另一個對象為止,程序最后將調用該對象的writeObject()方法來保存該對象的狀態
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException實現保護性復制整個對象,緊挨著readObject()之后被調用,該方法的返回值將會代替原來反序列化的對象,而原來readObject()反序列化的對象將會立即丟棄
Java的序列化機制:首先調用writeReplace(),其次調用writeObject(),最后調用writeResolve()
readObject()方法在序列化單例類,枚舉類時尤其有用
反序列化機制在恢復java對象時無須調用構造器來初始化java對象。從這個意義上來看,序列化機制可以用來"克隆"對象;所有單例類,枚舉類在實現序列化時都應該提供readResolve()方法,這樣才可以保證反序列化的對象依然正常;readResolve()方法建議使用final修飾
另一種自定義序列化機制這種序列化方式完全由程序員決定存儲和恢復對象數據。要實現該目標,必須實現Externalizable接口,該接口里定義了如下兩個方法:
void readExternal(ObjectInput in):需要序列化的類實現readExternal()方法來實現反序列化。該方法調用DataInput(它是ObjectInput的父接口)的方法來恢復基本類型的實例變量值,調用ObjectInput的readObject()方法來恢復引用類型的實例變量值
void writeExternal(Object out):需要序列化的類實現該方法來保存對象的狀態。該方法調用DataOutput(它是ObjectOutput的父接口)的方法來保存基本類型的實例變量值,調用ObjectOutput的writeObject()方法來保存引用類型的實例變量值
import java.io.*; public class Player implements java.io.Externalizable { private String name; private int number; // 注意此處沒有提供無參數的構造器! public Player(String name, int number) { System.out.println("有參數的構造器"); this.name = name; this.number = number; } // 省略name與number的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // number的setter和getter方法 public void setNumber(int number) { this.number = number; } public int getNumber() { return this.number; } public void writeExternal(java.io.ObjectOutput out) throws IOException { // 將name實例變量的值反轉后寫入二進制流 out.writeObject(new StringBuffer(name).reverse()); out.writeInt(number); } public void readExternal(java.io.ObjectInput in) throws IOException, ClassNotFoundException { // 將讀取的字符串反轉后賦給name實例變量 this.name = ((StringBuffer)in.readObject()).reverse().toString(); this.number = in.readInt(); } }
兩種序列化機制的對比
實現Serializable接口 | 實現Externalizable接口 |
---|---|
系統自動存儲必要信息 | 程序員決定存儲哪些信息 |
Java內建支持,易于實現,只需實現該接口即可,無須任何代碼支持 | 僅僅提供兩個空方法,實現該接口必須為兩個空方法提供實現 |
性能略差 | 性能略好 |
對象序列化的注意事項:
對象的類名、實例變量(包括基本類型、數組、對其他對象的引用)都會被序列化;方法、類變量(即static修飾的成員變量)、transient實例變量(也被稱為瞬態實例變量)都不會被序列化
反序列化讀取的僅僅是Java對象的數據,而不是Java類,因此采用反序列化恢復Java對象時,必須提供Java對象所屬的class文件,否則會引發ClassNotFoundException異常
實現Serializable接口的類如果需要讓某個實例變量不被序列化,則可以在該實例變量前加transient修飾符,而不是static關鍵字,雖然static關鍵字也可以達到這個效果,但static關鍵字不能這樣用
保證序列化對象的實例變量類型也是可序列化的,否則需要使用transient修飾該變量
當通過文件、網絡來讀取序列化后的對象時,必須按照實際寫入的順序讀取
版本隨著項目的設計,系統的class文件也會升級,Java如何保證兩個class文件的兼容性?為了在反序列化時確保序列化版本的兼容性,最好在每個要序列化的類中加入private static final long serialVersionUID這個屬性,具體數值自定義。這樣,即使某個類在與之對應的對象已經序列化出去后做了修改,該對象依然可以被正確反序列化
如不顯式定義該變量值,這個變量值將由JVM根據類的相關信息計算,而修改后的類的計算結果與修改前的類的計算結果往往不同,從而造成對象的反序列化因為類版本不兼容而失敗
導致該類實例的反序列化失敗的類修改操作:
如果修改類時僅僅修改了方法,則反序列化完全不受任何影響,類定義無需修改serizlVersionUID屬性值
如果修飾類時僅僅修改了靜態屬性或瞬態(transient)屬性,則反序列化不受任何影響,類定義無需修改serialVersionUID屬性值
如果修改類時修飾了非靜態、非瞬態屬性,則可能導致序列化版本不兼容,如果對象流中的對象和新類中包含同名的屬性,而屬性類型不同,則反序列化失敗 ,類定義應該更新serialVersionUID屬性值。如果新類比對象流中對象包含更多的 屬性,序列化版本也可以兼容,類定義可以不更新serialVersionUID屬性值;但反序列化得到的新對象中多出的屬性值都是null(引用類型屬性)或0(基本類型屬性)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66592.html
摘要:在中,對象的序列化與反序列化被廣泛應用到遠程方法調用及網絡傳輸中。相關接口及類為了方便開發人員將對象進行序列化及反序列化提供了一套方便的來支持。未實現此接口的類將無法使其任何狀態序列化或反序列化。 序列化與反序列化 序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。一般將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩沖等。在網絡傳輸過程中,可以...
摘要:序列化對象和平臺無關,序列化得到的字節流可以在任何平臺反序列化。從文件中或網絡上獲得序列化的字節流后,根據字節流中所保存的對象狀態及描述信息,通過反序列化重建對象。因此意味著不要序列化靜態變量不屬于對象狀態的一部分,因此它不參與序列化。 一.序列化和反序列化(1)序列化:將內存中的對象轉化為字節序列,用于持久化到磁盤中或者通過網絡傳輸。對象序列化的最主要的用處就是傳遞和保存對象,保證對...
摘要:使用對象序列化,在保存對象時,會把其狀態保存為一組字節,在未來,再將這些字節組裝成對象。由此可知,對象序列化不會關注類中的靜態變量。對象的讀寫類中對象的序列化工作是通過和來完成的。這就是為什么在此序列化過程中的無參構造器會被調用。 Java對象的序列化 Java平臺允許我們在內存中創建可復用的Java對象,但一般情況下,只有當JVM處于運行時,這些對象才可能存在,即,這些對象的生命周...
摘要:虛擬機讀取其他進程的數據對象的方法可以運行平臺上的其他程序該方法產生一個對象對象代表由該程序啟動啟動的子進程類提供如下三個方法用于和其子進程通信獲取子進程的錯誤流獲取子進程的輸入流獲取子進程的輸出流這里的輸入流輸出流容易混淆從程序的角度思考 Java虛擬機讀取其他進程的數據 Runtime對象的exec方法可以運行平臺上的其他程序,該方法產生一個Process對象,Process對象...
閱讀 1203·2021-11-17 09:33
閱讀 3599·2021-09-28 09:42
閱讀 3326·2021-09-13 10:35
閱讀 2478·2021-09-06 15:00
閱讀 2438·2021-08-27 13:12
閱讀 3609·2021-07-26 23:38
閱讀 1827·2019-08-30 15:55
閱讀 539·2019-08-30 15:53