摘要:使用對(duì)象序列化,在保存對(duì)象時(shí),會(huì)把其狀態(tài)保存為一組字節(jié),在未來,再將這些字節(jié)組裝成對(duì)象。由此可知,對(duì)象序列化不會(huì)關(guān)注類中的靜態(tài)變量。對(duì)象的讀寫類中對(duì)象的序列化工作是通過和來完成的。這就是為什么在此序列化過程中的無參構(gòu)造器會(huì)被調(diào)用。
Java對(duì)象的序列化
Java平臺(tái)允許我們?cè)趦?nèi)存中創(chuàng)建可復(fù)用的Java對(duì)象,但一般情況下,只有當(dāng)JVM處于運(yùn)行時(shí),這些對(duì)象才可能存在,即,這些對(duì)象的生命周期不會(huì)比JVM的生命周期更長。但在現(xiàn)實(shí)應(yīng)用中,就可能要求在JVM停止運(yùn)行之后能夠保存(持久化)指定的對(duì)象,并在將來重新讀取被保存的對(duì)象。Java對(duì)象序列化就能夠幫助我們實(shí)現(xiàn)該功能。
使用Java對(duì)象序列化,在保存對(duì)象時(shí),會(huì)把其狀態(tài)保存為一組字節(jié),在未來,再將這些字節(jié)組裝成對(duì)象。必須注意地是,對(duì)象序列化保存的是對(duì)象的”狀態(tài)”,即它的成員變量。由此可知,對(duì)象序列化不會(huì)關(guān)注類中的靜態(tài)變量。
簡而言之,就是讓對(duì)象想基本數(shù)據(jù)類型和字符串類型一樣,通過輸入輸出字節(jié)流ObjectInputStream 和 ObjectOutputStream進(jìn)行寫和讀操作。
Java序列化的應(yīng)用場景當(dāng)你想把的內(nèi)存中的對(duì)象狀態(tài)保存到一個(gè)文件中或者數(shù)據(jù)庫中時(shí)候
當(dāng)你想用套接字在網(wǎng)絡(luò)上傳送對(duì)象的時(shí)候
當(dāng)你想通過RMI傳輸對(duì)象的時(shí)候
如何對(duì)Java對(duì)象進(jìn)行序列化與反序列化在Java中,只要一個(gè)類實(shí)現(xiàn)了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(); } }
此時(shí)的輸出為:
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
此時(shí)必須注意的是,當(dāng)重新讀取被保存的User對(duì)象時(shí),并沒有調(diào)用User的任何構(gòu)造器,看起來就像是直接使用字節(jié)將User對(duì)象還原出來的。
當(dāng)User對(duì)象被保存到tempfile文件中之后,我們可以在其它地方去讀取該文件以還原對(duì)象,但必須確保該讀取程序的CLASSPATH中包含有User.class,否則會(huì)拋出ClassNotFoundException。
Q:之前不是說序列化不保存靜態(tài)變量么,為什么這里的靜態(tài)變量進(jìn)行了傳遞,都變成了10呢?
A:因?yàn)榇藭r(shí)User.class已經(jīng)被加載進(jìn)了內(nèi)存中,且將static變量test從1更改為了10。當(dāng)我們恢復(fù)對(duì)象時(shí),會(huì)直接獲取當(dāng)前static的變量test的值,所以為10。
Q:但如果我們的恢復(fù)操作在另一個(gè)文件中進(jìn)行,結(jié)果會(huì)怎么樣呢?
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(); } }
輸出結(jié)果為:
User{name="qiuyu", age=23, gender=null, birthday=Tue Nov 14 20:38:57 GMT+08:00 2017, testStatic=1} User@6442b0a6
A:因?yàn)樵谶\(yùn)行此代碼時(shí),User.class會(huì)被加載進(jìn)內(nèi)存,然后執(zhí)行初始化,將被初始化為1,因此test變量會(huì)被恢復(fù)為1。注意區(qū)分上面兩種情況,不要因?yàn)槭窃诒镜剡M(jìn)行測(cè)試,就認(rèn)為static會(huì)被序列化,同時(shí)要了解Java運(yùn)行時(shí),內(nèi)存加載的機(jī)制。
基本知識(shí)點(diǎn)Serializable接口
對(duì)于任何需要被序列化的對(duì)象,都必須要實(shí)現(xiàn)接口Serializable,它只是一個(gè)標(biāo)識(shí)接口,本身沒有任何成員,只是用來標(biāo)識(shí)說明當(dāng)前的實(shí)現(xiàn)類的對(duì)象可以被序列化.
如果父類實(shí)現(xiàn)序列化,子類自動(dòng)實(shí)現(xiàn)序列化,不需要顯式實(shí)現(xiàn)Serializable接口。
如果被寫對(duì)象的類型是String,數(shù)組,Enum,Serializable,那么就可以對(duì)該對(duì)象進(jìn)行序列化,否則將拋出NotSerializableException。
對(duì)象的讀寫
Java類中對(duì)象的序列化工作是通過 ObjectOutputStream 和 ObjectInputStream 來完成的。
使用readObject()、writeObject()方法對(duì)對(duì)象進(jìn)行讀寫操作;
對(duì)于基本類型,可以使用readInt()、writeInt() , readDouble()、writeDouble()等類似的接口進(jìn)行讀寫。
序列化機(jī)制
如果僅僅只是讓某個(gè)類實(shí)現(xiàn)Serializable接口,而沒有其它任何處理的話,則就是使用默認(rèn)序列化機(jī)制。使用默認(rèn)機(jī)制,在序列化對(duì)象時(shí),不僅會(huì)序列化當(dāng)前對(duì)象本身,還會(huì)對(duì)該對(duì)象引用的其它對(duì)象也進(jìn)行序列化,同樣地,這些其它對(duì)象引用的另外對(duì)象也將被序列化。
在現(xiàn)實(shí)應(yīng)用中,有些時(shí)候不能使用默認(rèn)序列化機(jī)制。比如,希望在序列化過程中忽略掉敏感數(shù)據(jù),或者簡化序列化過程。為此需要為某個(gè)字段聲明為transient,那么序列化機(jī)制就會(huì)忽略被transient修飾的字段。transient的引用變量會(huì)以null返回,基本數(shù)據(jù)類型會(huì)以相應(yīng)的默認(rèn)值返回。(例如:引用類型沒有實(shí)現(xiàn)Serializable,或者動(dòng)態(tài)數(shù)據(jù)只可以在執(zhí)行時(shí)求出而不能或不必存儲(chǔ))
writeObject()與readObject()對(duì)于上被聲明為transient的字段gender,除了將transient關(guān)鍵字去掉之外,是否還有其它方法能使它再次可被序列化?方法之一就是在User類中添加兩個(gè)方法: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()方法中會(huì)先調(diào)用ObjectOutputStream中的defaultWriteObject()方法,該方法會(huì)執(zhí)行默認(rèn)的序列化機(jī)制,此時(shí)會(huì)忽略掉gender字段。
然后再調(diào)用writeUtf()方法顯示地將gender字段寫入到ObjectOutputStream中。readObject()的作用則是針對(duì)對(duì)象的讀取,其原理與writeObject()方法相同。
Q: writeObject()與readObject()都是private方法,那么它們是如何被調(diào)用的呢?
A: 毫無疑問,是使用反射。(注意這不是繼承接口的方法,因?yàn)榻涌陬惖姆椒ǘ际莗ublic的,而這里的方法是private的)
無論是使用transient關(guān)鍵字,還是使用writeObject()和readObject()方法,其實(shí)都是基于Serializable接口的序列化。JDK中提供了另一個(gè)序列化接口Externalizable,使用該接口之后,之前基于Serializable接口的序列化機(jī)制就將失效。
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) } }
輸出結(jié)果為:
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,當(dāng)使用該接口時(shí),序列化的細(xì)節(jié)需要由程序員去完成writeExternal()與readExternal()方法的具體細(xì)節(jié),以及哪些狀態(tài)需要進(jìn)行序列化。
另外,若使用Externalizable進(jìn)行序列化,當(dāng)讀取對(duì)象時(shí),會(huì)調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個(gè)新的對(duì)象,然后再將被保存對(duì)象的字段的值分別填充到新對(duì)象中。這就是為什么在此序列化過程中UserExtern的無參構(gòu)造器會(huì)被調(diào)用。由于這個(gè)原因,實(shí)現(xiàn)Externalizable接口的類必須要提供一個(gè)無參的構(gòu)造器,且它的訪問權(quán)限為public。
注意事項(xiàng)讀取對(duì)象的順序必須與寫入的順序相同
如果有不能被序列化的對(duì)象,執(zhí)行期間就會(huì)拋出NotSerializableException異常
序列化時(shí),只對(duì)對(duì)象的狀態(tài)進(jìn)行保存,而不管對(duì)象的方法
靜態(tài)變量不會(huì)被序列化,因?yàn)樗械膶?duì)象共享同一份靜態(tài)變量的值
如果一個(gè)對(duì)象的成員變量是一個(gè)對(duì)象,那么這個(gè)對(duì)象的數(shù)據(jù)成員也會(huì)被保存還原,而且會(huì)是遞歸的方式(對(duì)象網(wǎng))。(序列化程序會(huì)將對(duì)象版圖上的所有東西儲(chǔ)存下來,這樣才能讓該對(duì)象恢復(fù)到原來的狀態(tài))
如果子類實(shí)現(xiàn)Serializable接口而父類未實(shí)現(xiàn)時(shí),父類不會(huì)被序列化,但此時(shí)父類必須有個(gè)無參構(gòu)造方法,否則會(huì)拋InvalidClassException異常;因?yàn)榉葱蛄谢瘯r(shí)會(huì)恢復(fù)原有子對(duì)象的狀態(tài),而父類的成員變量也是原有子對(duì)象的一部分。由于父類沒有實(shí)現(xiàn)序列化接口,即使沒有顯示調(diào)用,也會(huì)默認(rèn)執(zhí)行父類的無參構(gòu)造函數(shù)使變量初始化;
深入理解序列化ID的問題
serialVersionUID適用于JAVA的序列化機(jī)制。簡單來說,Java的序列化機(jī)制是通過判斷類的serialVersionUID來驗(yàn)證版本一致性的。
在進(jìn)行反序列化時(shí),JVM會(huì)把傳來的字節(jié)流中的serialVersionUID與本地相應(yīng)實(shí)體類的serialVersionUID進(jìn)行比較,如果相同就認(rèn)為是一致的,可以進(jìn)行反序列化,否則就會(huì)出現(xiàn)序列化版本不一致的異常,即是InvalidCastException。
序列化存儲(chǔ)規(guī)則
Java 序列化機(jī)制為了節(jié)省磁盤空間,具有特定的存儲(chǔ)規(guī)則,當(dāng)寫入文件的為同一對(duì)象時(shí),并不會(huì)再將對(duì)象的內(nèi)容進(jìn)行存儲(chǔ),而只是再次存儲(chǔ)一份引用;
序列化到同一個(gè)文件時(shí),如第二次修改了相同對(duì)象屬性值再次保存時(shí)候,虛擬機(jī)根據(jù)引用關(guān)系知道已經(jīng)有一個(gè)相同對(duì)象已經(jīng)寫入文件,因此只保存第二次寫的引用,所以讀取時(shí),都是第一次保存的對(duì)象,第二次進(jìn)行的修改將無效。
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(); }
輸出結(jié)果: 注意觀察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 會(huì)在文件開始的地方寫入一個(gè) Header的信息到文件中。于是在多次序列化的過程中就會(huì)繼續(xù)在文件末尾(本次序列化的開頭)寫入 Header 的信息,這時(shí)如果進(jìn)行反序列化的對(duì)象的時(shí)候會(huì)報(bào)錯(cuò):java.io.StreamCorruptedException: invalid type code: AC
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/68007.html
摘要:由以上結(jié)果分析可知,靜態(tài)變量不能被序列化,示例讀取出來的是在內(nèi)存中存儲(chǔ)的值。關(guān)鍵字總結(jié)修飾的變量不能被序列化只作用于實(shí)現(xiàn)接口只能用來修飾普通成員變量字段不管有沒有修飾,靜態(tài)變量都不能被序列化好了,棧長花了半天時(shí)間,終于整理完了。 先解釋下什么是序列化 我們的對(duì)象并不只是存在內(nèi)存中,還需要傳輸網(wǎng)絡(luò),或者保存起來下次再加載出來用,所以需要Java序列化技術(shù)。 Java序列化技術(shù)正是將對(duì)象轉(zhuǎn)...
摘要:對(duì)象序列化對(duì)象序列化機(jī)制允許把內(nèi)存中的對(duì)象轉(zhuǎn)換成與平臺(tái)無關(guān)的二進(jìn)制流,從而可以保存到磁盤或者進(jìn)行網(wǎng)絡(luò)傳輸,其它程序獲得這個(gè)二進(jìn)制流后可以將其恢復(fù)成原來的對(duì)象。 對(duì)象序列化 對(duì)象序列化機(jī)制允許把內(nèi)存中的Java對(duì)象轉(zhuǎn)換成與平臺(tái)無關(guān)的二進(jìn)制流,從而可以保存到磁盤或者進(jìn)行網(wǎng)絡(luò)傳輸,其它程序獲得這個(gè)二進(jìn)制流后可以將其恢復(fù)成原來的Java對(duì)象。 序列化機(jī)制可以使對(duì)象可以脫離程序的運(yùn)行而對(duì)立存在 ...
摘要:在中,對(duì)象的序列化與反序列化被廣泛應(yīng)用到遠(yuǎn)程方法調(diào)用及網(wǎng)絡(luò)傳輸中。相關(guān)接口及類為了方便開發(fā)人員將對(duì)象進(jìn)行序列化及反序列化提供了一套方便的來支持。未實(shí)現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化。 序列化與反序列化 序列化 (Serialization)是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^程。一般將一個(gè)對(duì)象存儲(chǔ)至一個(gè)儲(chǔ)存媒介,例如檔案或是記億體緩沖等。在網(wǎng)絡(luò)傳輸過程中,可以...
摘要:的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)象的數(shù)據(jù),有關(guān)對(duì)象的類型信息和存儲(chǔ)在對(duì)象中的數(shù)據(jù)類型。任何實(shí)現(xiàn)了接口的類都可以被序列化。一旦對(duì)象被序列化或者重新裝配,就會(huì)分別調(diào)用那兩個(gè)方法。 Java序列化 1. 什么是序列化? 序列化是將一個(gè)對(duì)象的狀態(tài),各屬性的值序列化保存起來,然后在合適的時(shí)候通過反序列化獲得。 Java的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)象...
摘要:的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)象的數(shù)據(jù),有關(guān)對(duì)象的類型信息和存儲(chǔ)在對(duì)象中的數(shù)據(jù)類型。這個(gè)是根據(jù)類名接口名成員方法及屬性等來生成一個(gè)位的哈希字段,因?yàn)樵黾恿俗侄?,因此生成的不一樣了? Java序列化 什么是序列化? 序列化是將一個(gè)對(duì)象的狀態(tài),各屬性的值序列化保存起來,然后在合適的時(shí)候通過反序列化獲得。 Java的序列化是將一個(gè)對(duì)象表示成字節(jié)序列,該字節(jié)序列包括了對(duì)...
摘要:從的序列化和反序列化說起序列化是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^程,而相反的過程就稱為反序列化。當(dāng)使用接口來進(jìn)行序列化與反序列化的時(shí)候需要開發(fā)人員重寫與方法。 從java的序列化和反序列化說起 序列化 (Serialization)是將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^程,而相反的過程就稱為反序列化。 在java中允許我們創(chuàng)建可復(fù)用的對(duì)象,但是這些對(duì)象僅僅存在j...
閱讀 4787·2021-09-26 09:55
閱讀 1500·2019-12-27 12:16
閱讀 1013·2019-08-30 15:56
閱讀 1959·2019-08-30 14:05
閱讀 1048·2019-08-30 13:05
閱讀 1321·2019-08-30 10:59
閱讀 1537·2019-08-26 16:19
閱讀 1941·2019-08-26 13:47