摘要:使用對象序列化,在保存對象時,會把其狀態保存為一組字節,在未來,再將這些字節組裝成對象。由此可知,對象序列化不會關注類中的靜態變量。對象的讀寫類中對象的序列化工作是通過和來完成的。這就是為什么在此序列化過程中的無參構造器會被調用。
Java對象的序列化
Java平臺允許我們在內存中創建可復用的Java對象,但一般情況下,只有當JVM處于運行時,這些對象才可能存在,即,這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象,并在將來重新讀取被保存的對象。Java對象序列化就能夠幫助我們實現該功能。
使用Java對象序列化,在保存對象時,會把其狀態保存為一組字節,在未來,再將這些字節組裝成對象。必須注意地是,對象序列化保存的是對象的”狀態”,即它的成員變量。由此可知,對象序列化不會關注類中的靜態變量。
簡而言之,就是讓對象想基本數據類型和字符串類型一樣,通過輸入輸出字節流ObjectInputStream 和 ObjectOutputStream進行寫和讀操作。
Java序列化的應用場景當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候
當你想用套接字在網絡上傳送對象的時候
當你想通過RMI傳輸對象的時候
如何對Java對象進行序列化與反序列化在Java中,只要一個類實現了java.io.Serializable接口,那么它就可以被序列化。
import java.io.*; import java.util.*; class User implements Serializable { private String name; private int age; private Date birthday; private transient String gender; private static int test =1; private static final long serialVersionUID = -6849794470754667710L; public User() { System.out.println("none-arg constructor"); } public User(String name, Integer age,Date birthday,String gender) { System.out.println("arg constructor"); this.name = name; this.age = age; this.birthday = birthday; this.gender = gender; } public void setTest(int Newtest) { this.test = Newtest; } 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 Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "User{" + "name="" + name + """ + ", age=" + age + ", gender=" + gender + ", birthday=" + birthday + ", testStatic="+test+ "}"+" "+super.toString(); } } public class SerializableDemo { public static void main(String[] args) throws Exception { //Initializes The Object User user = new User("qiuyu",23,new Date(),"male"); System.out.println(user); user.setTest(10); System.out.println(user); //Write Obj to File ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile")); out.writeObject(user); out.close(); //Read Obj from File ObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile")); User newUser = (User) in.readObject(); System.out.println(newUser); in.close(); } }
此時的輸出為:
arg constructor User{name="qiuyu", age=23, gender=male, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@326de728 User{name="qiuyu", age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=10} User@4883b407
此時必須注意的是,當重新讀取被保存的User對象時,并沒有調用User的任何構造器,看起來就像是直接使用字節將User對象還原出來的。
當User對象被保存到tempfile文件中之后,我們可以在其它地方去讀取該文件以還原對象,但必須確保該讀取程序的CLASSPATH中包含有User.class,否則會拋出ClassNotFoundException。
Q:之前不是說序列化不保存靜態變量么,為什么這里的靜態變量進行了傳遞,都變成了10呢?
A:因為此時User.class已經被加載進了內存中,且將static變量test從1更改為了10。當我們恢復對象時,會直接獲取當前static的變量test的值,所以為10。
Q:但如果我們的恢復操作在另一個文件中進行,結果會怎么樣呢?
import java.io.FileInputStream; import java.io.ObjectInputStream; public class Recovering { public static void main(String[] args) throws Exception{ //Read Obj from File ObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile")); User newUser = (User) in.readObject(); System.out.println(newUser); in.close(); } }
輸出結果為:
User{name="qiuyu", age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=1} User@6442b0a6
A:因為在運行此代碼時,User.class會被加載進內存,然后執行初始化,將被初始化為1,因此test變量會被恢復為1。注意區分上面兩種情況,不要因為是在本地進行測試,就認為static會被序列化,同時要了解Java運行時,內存加載的機制。
基本知識點Serializable接口
對于任何需要被序列化的對象,都必須要實現接口Serializable,它只是一個標識接口,本身沒有任何成員,只是用來標識說明當前的實現類的對象可以被序列化.
如果父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable接口。
如果被寫對象的類型是String,數組,Enum,Serializable,那么就可以對該對象進行序列化,否則將拋出NotSerializableException。
對象的讀寫
Java類中對象的序列化工作是通過 ObjectOutputStream 和 ObjectInputStream 來完成的。
使用readObject()、writeObject()方法對對象進行讀寫操作;
對于基本類型,可以使用readInt()、writeInt() , readDouble()、writeDouble()等類似的接口進行讀寫。
序列化機制
如果僅僅只是讓某個類實現Serializable接口,而沒有其它任何處理的話,則就是使用默認序列化機制。使用默認機制,在序列化對象時,不僅會序列化當前對象本身,還會對該對象引用的其它對象也進行序列化,同樣地,這些其它對象引用的另外對象也將被序列化。
在現實應用中,有些時候不能使用默認序列化機制。比如,希望在序列化過程中忽略掉敏感數據,或者簡化序列化過程。為此需要為某個字段聲明為transient,那么序列化機制就會忽略被transient修飾的字段。transient的引用變量會以null返回,基本數據類型會以相應的默認值返回。(例如:引用類型沒有實現Serializable,或者動態數據只可以在執行時求出而不能或不必存儲)
writeObject()與readObject()對于上被聲明為transient的字段gender,除了將transient關鍵字去掉之外,是否還有其它方法能使它再次可被序列化?方法之一就是在User類中添加兩個方法:writeObject()與readObject(),如下所示:
private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeUTF(gender); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); gender = in.readUTF(); }
在writeObject()方法中會先調用ObjectOutputStream中的defaultWriteObject()方法,該方法會執行默認的序列化機制,此時會忽略掉gender字段。
然后再調用writeUtf()方法顯示地將gender字段寫入到ObjectOutputStream中。readObject()的作用則是針對對象的讀取,其原理與writeObject()方法相同。
Q: writeObject()與readObject()都是private方法,那么它們是如何被調用的呢?
A: 毫無疑問,是使用反射。(注意這不是繼承接口的方法,因為接口類的方法都是public的,而這里的方法是private的)
無論是使用transient關鍵字,還是使用writeObject()和readObject()方法,其實都是基于Serializable接口的序列化。JDK中提供了另一個序列化接口Externalizable,使用該接口之后,之前基于Serializable接口的序列化機制就將失效。
import java.io.*; import java.util.*; class UserExtern implements Externalizable { private String name; private int age; private Date birthday; private transient String gender; private static int test =1; private static final long serialVersionUID = -6849794470754667710L; public UserExtern() { System.out.println("none-arg constructor"); } public UserExtern(String name, Integer age,Date birthday,String gender) { System.out.println("arg constructor"); this.name = name; this.age = age; this.birthday = birthday; this.gender = gender; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeUTF(gender); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); gender = in.readUTF(); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(name); out.writeInt(age); out.writeObject(birthday); out.writeUTF(gender); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); age = in.readInt(); birthday = (Date) in.readObject(); gender = in.readUTF(); } public void setTest(int Newtest) { this.test = Newtest; } 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 Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "User{" + "name="" + name + """ + ", age=" + age + ", gender=" + gender + ", birthday=" + birthday + ", testStatic="+test+ "}"+" "+super.toString(); } } public class ExternalizableDemo { public static void main(String[] args) throws Exception { UserExtern userExtern = new UserExtern("qiuyu",23,new Date(),"male"); System.out.println(userExtern); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("external")); out.writeObject(userExtern); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream("external")); UserExtern userExtern1 = (UserExtern)in.readObject(); System.out.println(userExtern1) } }
輸出結果為:
arg constructor User{name="qiuyu", age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@25618e91 none-arg constructor User{name="qiuyu", age=23, gender=male, birthday=Tue Nov 14 22:34:25 GMT+08:00 2017, testStatic=1} UserExtern@604ed9f0
Externalizable繼承于Serializable,當使用該接口時,序列化的細節需要由程序員去完成writeExternal()與readExternal()方法的具體細節,以及哪些狀態需要進行序列化。
另外,若使用Externalizable進行序列化,當讀取對象時,會調用被序列化類的無參構造器去創建一個新的對象,然后再將被保存對象的字段的值分別填充到新對象中。這就是為什么在此序列化過程中UserExtern的無參構造器會被調用。由于這個原因,實現Externalizable接口的類必須要提供一個無參的構造器,且它的訪問權限為public。
注意事項讀取對象的順序必須與寫入的順序相同
如果有不能被序列化的對象,執行期間就會拋出NotSerializableException異常
序列化時,只對對象的狀態進行保存,而不管對象的方法
靜態變量不會被序列化,因為所有的對象共享同一份靜態變量的值
如果一個對象的成員變量是一個對象,那么這個對象的數據成員也會被保存還原,而且會是遞歸的方式(對象網)。(序列化程序會將對象版圖上的所有東西儲存下來,這樣才能讓該對象恢復到原來的狀態)
如果子類實現Serializable接口而父類未實現時,父類不會被序列化,但此時父類必須有個無參構造方法,否則會拋InvalidClassException異常;因為反序列化時會恢復原有子對象的狀態,而父類的成員變量也是原有子對象的一部分。由于父類沒有實現序列化接口,即使沒有顯示調用,也會默認執行父類的無參構造函數使變量初始化;
深入理解序列化ID的問題
serialVersionUID適用于JAVA的序列化機制。簡單來說,Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。
在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException。
序列化存儲規則
Java 序列化機制為了節省磁盤空間,具有特定的存儲規則,當寫入文件的為同一對象時,并不會再將對象的內容進行存儲,而只是再次存儲一份引用;
序列化到同一個文件時,如第二次修改了相同對象屬性值再次保存時候,虛擬機根據引用關系知道已經有一個相同對象已經寫入文件,因此只保存第二次寫的引用,所以讀取時,都是第一次保存的對象,第二次進行的修改將無效。
public static void main(String[] args) throws Exception { //Initializes The Object User user = new User("qiuyu",23,new Date(),"male"); user.setTest(10); System.out.println(user); //Write Obj to File ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempFile")); out.writeObject(user); user.setAge(25); System.out.println(user); out.writeObject(user); out.close(); //Read Obj from File ObjectInputStream in = new ObjectInputStream(new FileInputStream("tempFile")); User newUser = (User) in.readObject(); User newUser1 = (User) in.readObject(); System.out.println(newUser); System.out.println(newUser1); in.close(); }
輸出結果: 注意觀察age的值
User{name="qiuyu", age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728 User{name="qiuyu", age=25, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@326de728 User{name="qiuyu", age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407 User{name="qiuyu", age=23, gender=male, birthday=Tue Nov 14 22:47:22 GMT+08:00 2017, testStatic=10} User@4883b407
多次序列化的問題
在一次的序列化的過程中,ObjectOutputStream 會在文件開始的地方寫入一個 Header的信息到文件中。于是在多次序列化的過程中就會繼續在文件末尾(本次序列化的開頭)寫入 Header 的信息,這時如果進行反序列化的對象的時候會報錯:java.io.StreamCorruptedException: invalid type code: AC
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68007.html
摘要:由以上結果分析可知,靜態變量不能被序列化,示例讀取出來的是在內存中存儲的值。關鍵字總結修飾的變量不能被序列化只作用于實現接口只能用來修飾普通成員變量字段不管有沒有修飾,靜態變量都不能被序列化好了,棧長花了半天時間,終于整理完了。 先解釋下什么是序列化 我們的對象并不只是存在內存中,還需要傳輸網絡,或者保存起來下次再加載出來用,所以需要Java序列化技術。 Java序列化技術正是將對象轉...
摘要:對象序列化對象序列化機制允許把內存中的對象轉換成與平臺無關的二進制流,從而可以保存到磁盤或者進行網絡傳輸,其它程序獲得這個二進制流后可以將其恢復成原來的對象。 對象序列化 對象序列化機制允許把內存中的Java對象轉換成與平臺無關的二進制流,從而可以保存到磁盤或者進行網絡傳輸,其它程序獲得這個二進制流后可以將其恢復成原來的Java對象。 序列化機制可以使對象可以脫離程序的運行而對立存在 ...
摘要:在中,對象的序列化與反序列化被廣泛應用到遠程方法調用及網絡傳輸中。相關接口及類為了方便開發人員將對象進行序列化及反序列化提供了一套方便的來支持。未實現此接口的類將無法使其任何狀態序列化或反序列化。 序列化與反序列化 序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。一般將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩沖等。在網絡傳輸過程中,可以...
摘要:的序列化是將一個對象表示成字節序列,該字節序列包括了對象的數據,有關對象的類型信息和存儲在對象中的數據類型。這個是根據類名接口名成員方法及屬性等來生成一個位的哈希字段,因為增加了字段,因此生成的不一樣了。 Java序列化 什么是序列化? 序列化是將一個對象的狀態,各屬性的值序列化保存起來,然后在合適的時候通過反序列化獲得。 Java的序列化是將一個對象表示成字節序列,該字節序列包括了對...
摘要:從的序列化和反序列化說起序列化是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程,而相反的過程就稱為反序列化。當使用接口來進行序列化與反序列化的時候需要開發人員重寫與方法。 從java的序列化和反序列化說起 序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程,而相反的過程就稱為反序列化。 在java中允許我們創建可復用的對象,但是這些對象僅僅存在j...
閱讀 4607·2021-09-26 09:55
閱讀 1352·2019-12-27 12:16
閱讀 879·2019-08-30 15:56
閱讀 1895·2019-08-30 14:05
閱讀 983·2019-08-30 13:05
閱讀 1261·2019-08-30 10:59
閱讀 1437·2019-08-26 16:19
閱讀 1880·2019-08-26 13:47