1、概念
序列化是Java提供的用于保存對象狀態(tài)的機制枪汪,可以將內(nèi)存中各種對象的狀態(tài)(實例變量)保存下來怀愧,并且將保存的對象狀態(tài)再讀出來。
2挫酿、何時需要序列化
- 當需要將內(nèi)存中對象的狀態(tài)保存到文件中或數(shù)據(jù)庫中時构眯,可以進行序列化;
- 當需要使用套接字在網(wǎng)絡上傳輸對象的時候早龟,可以進行序列化惫霸;
- 當需要通過RMI傳輸對象的時候,可以進行序列化葱弟;
3壹店、序列化方式
在Java中通過socket傳輸數(shù)據(jù)時,數(shù)據(jù)類型可以有很多種芝加。常見的做法有兩種:一是把對象包裝成JSON字符串傳輸硅卢,二是采用java對象的序列化和反序列化。
4藏杖、如何實現(xiàn)序列化
需要實現(xiàn)序列化的類可以通過實現(xiàn) java.io.Serializable 接口以啟用其序列化功能将塑,未實現(xiàn)此接口的類將無法使其任何狀態(tài)序列化或反序列化≈剖校可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段弊予,僅用于標識可序列化的語義祥楣。在反序列化過程中,將使用該類的公用或受保護的無參數(shù)構造方法初始化不可序列化類的字段∥笸剩可序列化的子類必須能夠訪問無參數(shù)的構造方法责鳍。可序列化子類的字段將從該流中還原兽间。
5历葛、serialVersionUID
Java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時嘀略,JVM會把傳來的字節(jié)流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較恤溶,如果相同就認為是一致的,可以進行反序列化帜羊,否則就會出現(xiàn)序列化版本不一致的異常咒程。serialVersionUID 用來表明類的不同版本間的兼容性。有兩種生成方式:
- 一個是默認的1L讼育,比如:private static final long serialVersionUID = 1L帐姻;
- 一個是根據(jù)類名、接口名奶段、成員方法及屬性等來生成一個64位的哈希字段饥瓷,比如: private static final long serialVersionUID = xxxxL;
6、序列化與反序列化
當兩個進程在進行遠程通信時痹籍,彼此可以發(fā)送各種類型的數(shù)據(jù)呢铆。無論是何種類型的數(shù)據(jù),都會以二進制序列的形式在網(wǎng)絡上傳送词裤。發(fā)送方需要把這個Java對象轉(zhuǎn)換為字節(jié)序列刺洒,才能在網(wǎng)絡上傳送;接收方則需要把字節(jié)序列再恢復為Java對象吼砂。
- 把Java對象轉(zhuǎn)換為字節(jié)序列的過程稱為對象的序列化逆航。
- 把字節(jié)序列恢復為Java對象的過程稱為對象的反序列化。
對象的序列化主要有兩種用途:(1)把對象的字節(jié)序列永久地保存到硬盤上渔肩,通常存放在一個文件中因俐; (2)在網(wǎng)絡上傳送對象的字節(jié)序列;
java.io.ObjectOutputStream代表對象輸出流周偎,它的writeObject(Object obj)方法可對參數(shù)指定的obj對象進行序列化抹剩,把得到的字節(jié)序列寫到一個目標輸出流中。 java.io.ObjectInputStream代表對象輸入流蓉坎,它的readObject()方法從一個源輸入流中讀取字節(jié)序列澳眷,再把它們反序列化為一個對象,并將其返回蛉艾。
只有實現(xiàn)了Serializable和Externalizable接口的類的對象才能被序列化钳踊。Externalizable接口繼承自Serializable接口衷敌,實現(xiàn)Externalizable接口的類完全由自身來控制序列化的行為,而僅實現(xiàn)Serializable接口的類可以采用默認的序列化方式 拓瞪。
凡是實現(xiàn)Serializable接口的類都有一個表示序列化版本標識符的靜態(tài)變量:private static final long serialVersionUID;
序列化運行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯(lián)缴罗,該序列號在反序列化過程中用于驗證序列化對象的發(fā)送者和接收者是否為該對象加載了與序列化兼容的類。如果接收者加載的該對象的類的 serialVersionUID 與對應的發(fā)送者的類的版本號不同祭埂,則反序列化將會導致 InvalidClassException面氓。可序列化類可以通過聲明名為serialVersionUID的字段(該字段必須是靜態(tài) (static)蛆橡、最終 (final) 的 long 型字段)顯式聲明其自己的 serialVersionUID:
7舌界、序列化機制
序列化分為兩大部分:序列化和反序列化。序列化是這個過程的第一部分航罗,將數(shù)據(jù)分解成字節(jié)流禀横,以便存儲在文件中或在網(wǎng)絡上傳輸。反序列化就是打開字節(jié)流并重構對象粥血。對象序列化不僅要將基本數(shù)據(jù)類型轉(zhuǎn)換成字節(jié)表示柏锄,有時還要恢復數(shù)據(jù)「纯鳎恢復數(shù)據(jù)要求有恢復數(shù)據(jù)的對象實例趾娃。ObjectOutputStream中的序列化過程與字節(jié)流連接,包括對象類型和版本信 息缔御。反序列化時抬闷,JVM用頭信息生成對象實例,然后將對象字節(jié)流中的數(shù)據(jù)復制到對象數(shù)據(jù)成員中耕突。ava.io包有兩個序列化對象的類笤成。ObjectOutputStream負責將對象寫入字節(jié)流,ObjectInputStream從字節(jié)流重構對象眷茁。序列化時炕泳,類的所有數(shù)據(jù)成員應可序列化除了聲明為transient或static的成員。將變量聲明為transient告訴JVM我們會負責將變元序列 化上祈。將數(shù)據(jù)成員聲明為transient后培遵,序列化過程就無法將其加進對象字節(jié)流中,沒有從transient數(shù)據(jù)成員發(fā)送的數(shù)據(jù)登刺。后面數(shù)據(jù)反序列化時籽腕, 要重建數(shù)據(jù)成員(因為它是類定義的一部分),但不包含任何數(shù)據(jù)纸俭,因為這個數(shù)據(jù)成員不向流中寫入任何數(shù)據(jù)皇耗。記住,對象流不序列化static或 transient揍很。我們的類要用writeObject()與readObject()方法以處理這些數(shù)據(jù)成員郎楼。使用writeObject()與 readObject()方法時矾瘾,還要注意按寫入的順序讀取這些數(shù)據(jù)成員。
8箭启、序列化的完全定制
如果一個類要完全負責自己的序列化,則實現(xiàn)Externalizable接口而不是Serializable接口蛉迹。Externalizable接口定義包 括兩個方法writeExternal()與readExternal()傅寡。利用這些方法可以控制對象數(shù)據(jù)成員如何寫入字節(jié)流.類實現(xiàn) Externalizable時,頭寫入對象流中北救,然后類完全負責序列化和恢復數(shù)據(jù)成員荐操,除了頭以外,根本沒有自動序列化珍策。這里要注意了托启。聲明類實現(xiàn) Externalizable接口會有重大的安全風險。writeExternal()與readExternal()方法聲明為public攘宙,惡意類可 以用這些方法讀取和寫入對象數(shù)據(jù)屯耸。如果對象包含敏感信息,則要格外小心蹭劈。這包括使用安全套接或加密整個字節(jié)流疗绣。
9、注意事項
- 序列化時铺韧,只對對象的狀態(tài)進行保存多矮,而不管對象的方法;
- 當一個父類實現(xiàn)序列化哈打,子類自動實現(xiàn)序列化塔逃,不需要顯式實現(xiàn)Serializable接口;
- 當一個對象的實例變量引用其他對象料仗,序列化該對象時也把引用對象進行序列化湾盗;