Java基礎(chǔ)入門總結(jié)之序列化和反序列化
基本概念
- Java中創(chuàng)建對象時(shí),一旦程序終止,創(chuàng)建的對象可能就不存在.要想使得對象能夠在程序不運(yùn)行的狀態(tài)下依然能夠保存對象的信息,這時(shí)就需要用到序列化機(jī)制
- 序列化機(jī)制:
- 一個(gè)對象可以被表示為一個(gè)字節(jié)序列,包括:
- 對象的數(shù)據(jù)
- 對象的類型信息
- 存儲在對象中的數(shù)據(jù)類型
- 將可序列化對象寫入文件后,可以從文件中讀取出來,根據(jù)對象的各種信息在內(nèi)存中創(chuàng)建該對象. 這里的讀取并創(chuàng)建對象的過程就是反序列化
- 序列化和反序列化的整個(gè)過程都是JVM獨(dú)立的.也就是說,在一個(gè)JVM中的序列化對象可以在另一個(gè)完全不同的JVM中反序列化對象
- 一般情況下,序列化需要實(shí)現(xiàn)java.io.Serializable接口,使用ObjectInputStream和ObjectOutputStream進(jìn)行對象的讀寫操作
- 還可以實(shí)現(xiàn)java.io.Externalizable接口,進(jìn)行標(biāo)準(zhǔn)的序列化或者自定義的二進(jìn)制格式.用來滿足不同場景下的需求
- 一個(gè)對象可以被表示為一個(gè)字節(jié)序列,包括:
- Java序列化場景:
- 將Java對象的字節(jié)序列持久化到硬盤中
- 在網(wǎng)絡(luò)上傳輸對象的字節(jié)序列
- 進(jìn)行遠(yuǎn)程方法調(diào)用RMI(Remote Method Invocation)
- JVM運(yùn)行結(jié)束時(shí),還需要使用創(chuàng)建的對象
- 需要將創(chuàng)建的對象保存下來以便后續(xù)的傳輸
- 使得舊JVM創(chuàng)建的對象能夠在一個(gè)新的JVM中運(yùn)行
- Java序列化注意點(diǎn):
- 對象的序列化保存的是對象成員變量對象,對象的序列化不會關(guān)注類中的靜態(tài)變量
- 類的序列化要保證類的所有屬性都是可以序列化的,如果想要某個(gè)屬性不被序列化.可以聲明為瞬時(shí)態(tài)transient
序列化
- Java對象序列化:
- 使得可序列化的對象實(shí)現(xiàn)Serializable接口
- 創(chuàng)建一個(gè)ObjectOutputStream輸出流
- 調(diào)用ObjectOutputStream對象的writeObject() 方法進(jìn)行輸出可序列化對象即可
- 序列化示例:
public class Person implements Serializable { private String name; private int age; public Person() { System.out.println("無參構(gòu)造..."); } public Person(String name, int age) { this.name = name; this.age = age; System.out.println("有參構(gòu)造..."); } @Override public String toString() { return "Person{" + "name='" + name + "\'" + ", age='" + age + "\'" "}"; } }
public class SerializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = new Person("Lily", 20); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.txt")); oos.writeObject(person); oos.close(); } }
反序列化
- Java對象反序列化:
- 創(chuàng)建一個(gè)ObjectInputStream輸入流
- 調(diào)用ObjectInputStream對象的readObject() 方法得到序列化對象
public class DeserializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt")); Pesron person = (Person)ois.readObject(ois); System.out.println(person); } }
- 反序列化的對象是由JVM生成的對象,而不是通過類的構(gòu)造函數(shù)生成的:
- 反序列化對象時(shí) ,JVM中要存在對象對應(yīng)的類,否則會拋出ClassNotFoundException異常
- 如果一個(gè)可序列化的類的成員不是基本類型,而是一個(gè)引用類型時(shí),那么這個(gè)引用類型必須實(shí)現(xiàn)Serializable接口,否則會拋出NotSerializableException異常
序列化和反序列化總結(jié)
- 實(shí)現(xiàn)Serializable接口就可以進(jìn)行序列化的原因:
- writeObject():
- 首先會處理之前被編寫的以及不可替換的對象
- 如果有的對象被替換了,則檢查被替換的對象
- 最后如果對象都被替換了,則進(jìn)行原始的檢查
- 原始的檢查即檢查被替換的對象類型是否為String類型,數(shù)組類型 ,Enum類型或者實(shí)現(xiàn)了Serializable接口,符合條件就可以對檢查的對象執(zhí)行相應(yīng)的序列化操作,否則將會拋出NotSerializableException異常
- 對于序列化的機(jī)制來說,如果對同一個(gè)對象執(zhí)行多次序列化操作時(shí),不會得到多個(gè)對象
- 保存到磁盤的對象都有一個(gè)序列化編號,當(dāng)程序試圖進(jìn)行序列化時(shí),會檢查該對象是否已經(jīng)序列化
- 只有對象從未被序列化過時(shí),才會將此對象序列化為字節(jié)序列,如果對象已經(jīng)序列化過,那么直接輸出序列化編號
- Java序列化機(jī)制不會重復(fù)序列化同一個(gè)對象,會記錄已經(jīng)序列化對象的編號,此時(shí)如果序列化了一個(gè)可變對象后,如果更改了對象的內(nèi)容會再次進(jìn)行序列化.如果沒有更改內(nèi)容,則不會將此對象轉(zhuǎn)換為字節(jié)序列,只會保存序列化編號
- writeObject():
- 實(shí)現(xiàn)Serializable接口時(shí)可以重寫writeObject() 方法和readObject() 方法:
- 重寫writeObject() 方法和readObject() 方法后,對象進(jìn)行序列化和反序列化時(shí),就會自動調(diào)用重寫的writeObject() 方法和readObject() 方法
- 實(shí)現(xiàn)Externalizable接口時(shí)可以重寫writeExternal() 方法和readExternal() 方法:
- 重寫writeExternal() 方法和readExternal() 方法后,對象進(jìn)行序列化和反序列化時(shí),就會自動調(diào)用重寫的writeExternal() 方法和readExternal() 方法
自定義序列化策略
Externalizable
- 如果需要使得對象的一部分可以被序列化,另一部分?jǐn)?shù)據(jù)不被序列化,此時(shí)可以自定義實(shí)現(xiàn)Externalizable接口,并且實(shí)現(xiàn)writeExternal() 和readExternal() 方法,可以在序列化和反序列化過程中自動調(diào)用來執(zhí)行一些特殊的操作
- 注意點(diǎn):
- Serializable接口實(shí)現(xiàn)的對象是與二進(jìn)制的構(gòu)建有關(guān)的,不會調(diào)用構(gòu)造器
- Externalizable接口實(shí)現(xiàn)的對象的所有構(gòu)造函數(shù)都會被調(diào)用,所以要編寫出類的無參和有參構(gòu)造函數(shù)
- 使用Externalizable自定義序列化示例:
public class CustomExternal implements Externalizable { private String name; private int code; public CustomExternal() { System.out.println("無參構(gòu)造..."); } public CustomExternal(String name, int code) { this.name = name; this.code = code; System.out.println("有參構(gòu)造..."); } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println("執(zhí)行writeExternal()方法..."); out.writeObject(name); out.writeInt(code); } @Override public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException { System.out.println("執(zhí)行readExternal()方法..."); name = (String) in.readObject(); code = in.readInt(); } @Override public String toString() { return "類:" + name + code; } public static void main(String[] args) throws IOException, ClassNotFoundException { CustomExternal custom = new CustomeExternal("oxford", 666); System.out.println(custom); // 序列化 ObjectOutputStream out = new ObjectOutputStream(new FileInputStream("oxford.txt")); System.out.println("序列化對象..."); out.writeObject(custom); out.close(); // 反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream("oxford.txt")); System.out.println("反序列化對象..."); custom = (CustomExternal) in.readObject(); System.out.println(custom); in.close(); } }
有參構(gòu)造...
類:oxford666
序列化對象...
執(zhí)行writeExternal()方法...
反序列化對象...
無參構(gòu)造...
執(zhí)行readExternal()方法...
類:oxford666
- 使用Externalizable自定義序列化時(shí),為了保證序列化和反序列化的正確性,需要在writeExternal() 方法中將信息寫入,并且在readExternal() 方法中恢復(fù)數(shù)據(jù)
transient
- 可以使用transient關(guān)鍵字配置一些重要的信息比如密碼等不進(jìn)行序列化
- transient關(guān)鍵字修飾的屬性不會參與到序列化過程中
- transient關(guān)鍵字修飾的屬性在反序列化過程中,如果是引用數(shù)據(jù)類型,則返回null. 如果是基本數(shù)據(jù)類型,則返回默認(rèn)值.不一定是基本數(shù)據(jù)類型序列化之前的值
- 因?yàn)閷?shí)現(xiàn)Externalizable接口的對象默認(rèn)情況下不會保存任何字段,所以transient關(guān)鍵字只能和Serializable對象一起使用
- transient關(guān)鍵字的使用場景:
- 服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù)時(shí),對象中存在敏感數(shù)據(jù)
- 比如密碼字符串,在序列化時(shí)進(jìn)行加密,客戶端擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時(shí),才會對密碼進(jìn)行讀取
- 這時(shí)就可以對密碼字符串對象使用transient修飾,這樣可以一定程度上保證序列化對象的數(shù)據(jù)安全
靜態(tài)變量
- 序列化時(shí)不會序列化靜態(tài)變量
- 靜態(tài)變量屬于類的狀態(tài),序列化中保存的是對象,也就是類的實(shí)例的狀態(tài)
- 序列化操作的是序列化中對象,也就是類的實(shí)例的狀態(tài),靜態(tài)變量屬于類的狀態(tài).所以序列化時(shí)不會對靜態(tài)變量進(jìn)行序列化
序列化ID
- Java虛擬機(jī)進(jìn)行反序列化:
- 兩個(gè)類的類路徑和功能代碼一致
- 兩個(gè)類的序列化ID,也就是serialVersionUID一致
- 功能代碼一致:
- 序列化的類和反序列化的類所實(shí)現(xiàn)的功能和功能相關(guān)的代碼是一樣的
- 示例:
- 客戶端A將類對象序列化客戶端B, 客戶端B進(jìn)行反序列化
- 這時(shí)要求A和B都有這樣的一個(gè)類文件,功能代碼一致,并且都實(shí)現(xiàn)了Serializable接口
- serialVersionUID的兩種生成方式:
- 默認(rèn)值 1L
- 通過類名,接口名和方法名以及屬性隨機(jī)生成的一個(gè)不重復(fù)的long類型的值
- 在序列化ID, 即serialVersionUID相同的情況下,即使序列化對象的序列化屬性修改,序列化對象也可以進(jìn)行反序列化.因此如果只是修改了方法或者修改了靜態(tài)變量或transient變量,只要不修改序列化ID, 那么反序列化就不會受到影響
- 顯式聲明序列化ID, 即serialVersionUID的場景:
- 如果需要類的不同版本對序列化兼容,要確保類的不同版本具有相同的serialVersionUID
- 如果不需要類的不同版本對序列化兼容,要確保類的不同版本具有不同的serialVersionUID
- 序列化一個(gè)類的實(shí)例后,如果修改一個(gè)字段或者增加一個(gè)字段,如果沒有設(shè)置類的serialVersionUID, 就會導(dǎo)致無法反序列化舊的實(shí)例,會在反序列化時(shí)拋出異常
- 序列化類添加SerialVersionUID后,如果修改一個(gè)字段或者增加一個(gè)字段,反序列化舊的實(shí)例時(shí),修改的或者增加的字段的值會設(shè)置為初始化的值
破壞單例
- 除了反射可以破壞單例模式外,序列化和反序列化后會得到一個(gè)新的對象,也可以破壞單例模式
- 序列化和反序列化破壞單例模式:
- 反序列化時(shí),使用ObjectInputStream對象中的readObject() 方法
- readObject() 的方法中調(diào)用readObject0() 方法
public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } int outerHandle = passHandle; try { Object obj = readObject0(false); handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); if (ex != null) { throw ex; } if (depth == 0) { vlist.doCallbacks(); } return obj; } finally { passHandle = outerHandle; if (closed && depth == 0) { clear(); } } }
- readObject0() 方法中會返回一個(gè)checkResolve(readOrdinaryObject(unshared))
private Object readObject0(boolean unshared) throws IOException { boolean oldMode = bin.getBlockDataMode(); if (oldMode) { int remain = bin.currentBlockRemaining(); if (remain > 0) { throw new OptionalDataException(remain); } else if (defaultDataEnd) { throw new OptionalDataException(true); } bin.setBlockDataMode(false); } byte tc; while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); } depth++; totalObjectRefs++; try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException("writing aborted", ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true); bin.peek(); throw new OptionalDataException( bin.currentBlockRemaining()); } else { throw new StreamCorruptedException( "unexpected block data"); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException(true); } else { throw new StreamCorruptedException( "unexpected end of block data"); } default: throw new StreamCorruptedException( String.format("invalid type code: %02X", tc)); } } finally { depth--; bin.setBlockDataMode(oldMode); } }
- readOrdinaryObject() 方法用于讀取并返回普通對象. 這里的普通對象不包括String, Class, ObjectStreamClass, 數(shù)組或者枚舉常量這些對象
private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; }
- isInstantiable() 方法表示如果一個(gè)實(shí)現(xiàn)了Serializable接口或者Externalizable接口的類可以在運(yùn)行時(shí)實(shí)例化,那么該方法就返回true
- 如果可以在運(yùn)行時(shí)序列化,就會調(diào)用desc.newInstance() 方法使用反射的方式調(diào)用無參構(gòu)造方法新建一個(gè)對象,創(chuàng)建一個(gè)類的新的實(shí)例
- 如果類實(shí)現(xiàn)的是Serializable接口,就調(diào)用第一個(gè)不可進(jìn)行序列化超類的無參構(gòu)造方法數(shù)創(chuàng)建新的實(shí)例
- 如果類實(shí)現(xiàn)的Externalizable接口,就調(diào)用公共的無參構(gòu)造方法創(chuàng)建新的實(shí)例
- 因?yàn)樾蛄谢^程中會通過反射調(diào)用無參構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對象,所以序列化會破壞單例模式
- 為了防止序列化破壞單例模式,可以在Singleton.java中添加readResolve() 方法并且指定要返回的對象的生成策略
- 因?yàn)?strong>readOrdinaryObject() 方法源碼中的hasResolveMethod() 表示如果實(shí)現(xiàn)了Serializable或者Externalizable接口的類中包含readResolve() 方法就返回true
- invokeReadResolve() 方法會通過反射的方式調(diào)用要被反序列化的類的readResolve() 方法
總結(jié)
到此這篇關(guān)于Java基礎(chǔ)入門之序列化和反序列化的文章就介紹到這了,更多相關(guān)Java序列化和反序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于在IDEA熱部署插件JRebel使用問題詳解
這篇文章主要介紹了關(guān)于在IDEA熱部署插件JRebel使用問題詳解,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12java實(shí)現(xiàn)百度坐標(biāo)的摩卡托坐標(biāo)與火星坐標(biāo)轉(zhuǎn)換的示例
這篇文章主要介紹了java實(shí)現(xiàn)百度坐標(biāo)的摩卡托坐標(biāo)與火星坐標(biāo)轉(zhuǎn)換的示例,需要的朋友可以參考下2014-03-03Maven中央倉庫發(fā)布的實(shí)現(xiàn)方法
最近做了個(gè)項(xiàng)目,希望能夠上傳到maven中央倉庫,給更多的人使用,于是就產(chǎn)生了這次項(xiàng)目發(fā)布經(jīng)歷。感興趣的可以一起來參考一下2021-06-06spring的構(gòu)造函數(shù)注入屬性@ConstructorBinding用法
這篇文章主要介紹了關(guān)于spring的構(gòu)造函數(shù)注入屬性@ConstructorBinding用法,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12SpringBoot打印POST請求原始入?yún)ody體方式
這篇文章主要介紹了SpringBoot打印POST請求原始入?yún)ody體方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09Spring @value和@PropertySource注解使用方法解析
這篇文章主要介紹了Spring @value和@PropertySource注解使用方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11