java序列化與反序列化

序列化的意義

1.永久存儲某個jvm中運行時的對象。
2.對象可以網(wǎng)絡(luò)傳輸
3.rmi調(diào)用都是以序列化的方式傳輸參數(shù)

基本知識

1.在Java中,只要一個類實現(xiàn)了java.io.Serializable接口,那么它就可以被序列化。
2.通過ObjectOutputStream和ObjectInputStream對對象進(jìn)行序列化及反序列化

3.虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
4.序列化并不保存靜態(tài)變量。
5.要想將父類對象也序列化,就需要讓父類也實現(xiàn)Serializable 接口。
6.Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對象型的是 null。
7.服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù),對象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進(jìn)行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時,才可以對密碼進(jìn)行讀取,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全。(自定義實現(xiàn)readObject和writeObject)

一些雜事

1.將統(tǒng)一個對象連續(xù)多次寫入一個文件,Java 序列化機(jī)制為了節(jié)省磁盤空間,具有特定的存儲規(guī)則,當(dāng)寫入文件的為同一對象時,并不會再將對象的內(nèi)容進(jìn)行存儲,而只是再次存儲一份引用,上面增加的 5 字節(jié)的存儲空間就是新增引用和一些控制信息的空間。反序列化時,恢復(fù)引用關(guān)系,使得清單 3 中的 t1 和 t2 指向唯一的對象,二者相等,輸出 true。該存儲規(guī)則極大的節(jié)省了存儲空間。

    ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
    Test test = new Test();
    //試圖將對象兩次寫入文件
    out.writeObject(test);
    out.flush();
    System.out.println(new File("result.obj").length());
    out.writeObject(test);
    out.close();
    System.out.println(new File("result.obj").length());

    ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
            "result.obj"));
    //從文件依次讀出兩個文件
    Test t1 = (Test) oin.readObject();
    Test t2 = (Test) oin.readObject();
    oin.close();
            
    //判斷兩個引用是否指向同一個對象
    System.out.println(t1 == t2);

2.arraylist源碼,elementData為什么是transient的

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    transient Object[] elementData; // non-private to simplify nested class access
    private int size;
}

ArrayList實際上是動態(tài)數(shù)組,每次在放滿以后自動增長設(shè)定的長度值,如果數(shù)組自動增長長度設(shè)為100,而實際只放了一個元素,那就會序列化99個null元素。為了保證在序列化的時候不會將這么多null同時進(jìn)行序列化,ArrayList把元素數(shù)組設(shè)置為transient。

3.自定義序列化方式writeObject,readObject
以arraylist的readObject為例

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject(); //如果不自己定義readObject方法,默認(rèn)就是調(diào)用ObjectInputStream的defaultReadObject方法。

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

Serializable的幾個自定義神器方法

一. 解決反序列化異常的神器——Serializable接口的readObjectNoData方法

  • 方法定義:private void readObjectNoData() throws ObjectStreamException
  • 這個方法可能會讓你摸不著頭腦,測試的時候怎么都不會被執(zhí)行?,哈哈哈!這里是真相:如果有一個類A正在被反序列化,S是它現(xiàn)在的父類,readObjectNoData方法也應(yīng)該存在與S類中。當(dāng)發(fā)生以下條件時,將觸發(fā)這個readObjectNoData方法被執(zhí)行:
    1.S類直接或間接實現(xiàn)了Serializable接口
    2.S類必須定義實現(xiàn)readObjectNoData方法
    3.當(dāng)前待被反序列化的stream在被序列化時A類還沒有進(jìn)程S類。
    The readObjectNoData() method is analogous to the class-defined readObject() method, except that (if defined) it is called in cases where the class descriptor for a superclass of the object being deserialized (and hence the object data described by that class descriptor) is not present in the serialization stream.
    Note that readObjectNoData() is never invoked in cases where a class-defined readObject() method could be called, though serializable class implementors can call readObjectNoData() from within readObject() as a means of consolidating initialization code.

二. writeReplace,如果實現(xiàn)了writeReplace方法后,那么在序列化時會先調(diào)用writeReplace方法將當(dāng)前對象替換成另一個對象(該方法會返回替換后的對象)并將其寫入流中,例如:

class Person implements Serializable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return name + "(" + age + ")";
    }
    
    private Object writeReplace() throws ObjectStreamException {
        ArrayList<Object> list = new ArrayList<>();
        list.add(name);
        list.add(age);
        return list;
    }
    
}

在這里就將該對象直接替換成了一個list保存;

  • 實現(xiàn)writeReplace就不要實現(xiàn)writeObject了,實現(xiàn)了也不會被調(diào)用;
  • writeReplace的返回值(對象)必須是可序列話的,如果是Java自己的基礎(chǔ)類或者類型那就不用說了;
  • writeReplace替換后在反序列化時就無法被恢復(fù)了,即對象被徹底替換了!也就是說使用ObjectInputStream讀取的對象只能是被替換后的對象,只能在讀取后自己手動恢復(fù)了,接著上例的演示:
class Person implements Serializable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return name + "(" + age + ")";
    }
    
    private Object writeReplace() throws ObjectStreamException {
        ArrayList<Object> list = new ArrayList<>();
        list.add(name);
        list.add(age);
        return list;
    }
}

public class Test {
    
    public static void print(String s) {
        System.out.println(s);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.buf"))) {
            Person p = new Person("lala", 33);
            oos.writeObject(p);
            oos.close();
        }
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.buf"))) {
            print(((ArrayList)ois.readObject()).toString());
        }
    }
}

  • 使用writeReplace替換寫入后也不能通過實現(xiàn)readObject來實現(xiàn)自動恢復(fù)了,即下面的代碼是錯誤的!因為默認(rèn)已經(jīng)被徹底替換了,就不存在自定義反序列化的問題了,直接自動反序列化成ArrayList了,該方法即使實現(xiàn)了也不會調(diào)用!

class Person implements Serializable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return name + "(" + age + ")";
    }
    
    private Object writeReplace() throws ObjectStreamException {
        ArrayList<Object> list = new ArrayList<>();
        list.add(name);
        list.add(age);
        return list;
    }
    
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 錯!寫了也沒用
        ArrayList<Object> list = (ArrayList)in.readObject();
        this.name = (String)list.get(0);
        this.age = (int)list.get(1);
    }
}

所以,writeObject只和readObject配合使用,一旦實現(xiàn)了writeReplace在寫入時進(jìn)行替換就不再需要writeObject和readObject了!因為替換就已經(jīng)是徹底的自定義了,比writeObject/readObject更徹底!

三. 保護(hù)性恢復(fù)對象(同時也可以替換對象)——readResolve:

  • readResolve會在readObject調(diào)用之后自動調(diào)用,它最主要的目的就是讓恢復(fù)的對象變個樣,比如readObject已經(jīng)反序列化好了一個Person對象,那么就可以在readResolve里再對該對象進(jìn)行一定的修改,而最終修改后的結(jié)果將作為ObjectInputStream的readObject的返回結(jié)果;
  • 可以返回一個非原類型的對象,也就是說可以徹底替換對象
class Person implements Serializable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return name + "(" + age + ")";
    }
    
    private Object readResolve() throws ObjectStreamException { // 直接替換成一個int的1返回
        return 1;
    }
}
  • 其最重要的應(yīng)用就是保護(hù)性恢復(fù)單例、枚舉類型的對象!

枚舉問題示例:

class Brand implements Serializable {  
    private int val;  
    private Brand(int val) {  
        this.val = val;  
    }  
      
    // 兩個枚舉值  
    public static final Brand NIKE = new Brand(0);  
    public static final Brand ADDIDAS = new Brand(1);  
}  
  
public class Test {  
      
    public static void print(String s) {  
        System.out.println(s);  
    }  
  
    public static void main(String[] args) throws IOException, ClassNotFoundException {  
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.buf"))) {  
            oos.writeObject(Brand.NIKE);  
            oos.close();  
        }  
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.buf"))) {  
            Brand b = (Brand)ois.readObject();  
            print("" + (b == Brand.NIKE)); // 答案顯然是false  !!!!!!!!!因為Brand.NIKE是程序中創(chuàng)建的對象,而b是從磁盤中讀取并恢復(fù)過來的對象,兩者明顯來源不同,因此必然內(nèi)存空間是不同的,引用(地址)顯然也是不同的
        }  
    }  
}  

使用readResolve解決這個問題:

class Brand implements Serializable {  
    private int val;  
    private Brand(int val) {  
        this.val = val;  
    }  
      
    // 兩個枚舉值  
    public static final Brand NIKE = new Brand(0);  
    public static final Brand ADDIDAS = new Brand(1);  
    
    //這樣就能保證反序列化回來的枚舉能相等。  
    private Object readResolve() throws ObjectStreamException {  
        if (val == 0) {  
            return NIKE;  
        }  
        if (val == 1) {  
            return ADDIDAS;  
        }  
        return null;  
    }  
}  

四. 強(qiáng)制自己實現(xiàn)串行化和反串行化——Externalizable接口

  • 不像Serializable接口只是一個標(biāo)記接口,里面的接口方法都是可選的(可實現(xiàn)可不實現(xiàn),如果不實現(xiàn)則啟用其自動序列化功能),而Externalizable接口不是一個標(biāo)記接口,它強(qiáng)制你自己動手實現(xiàn)串行化和反串行化算法:
    
 //可以看到該接口直接繼承了Serializable接口,其兩個方法其實就對應(yīng)了Serializable的writeObject和readObject方法,實現(xiàn)方式也是一模一樣;  
 public interface Externalizable extends java.io.Serializable {  
        void writeExternal(ObjectOutput out) throws IOException; // 串行化  
        void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; // 反串行化  
}  

  • ObjectOutput和ObjectInput的使用方法和ObjectOutputStream和ObjectInputStream一模一樣(readObject、readInt之類的,完全一模一樣),因此Externalizable就是強(qiáng)制實現(xiàn)版的Serializable罷了;
class Person implements Externalizable {  
    private String name;  
    private int age;  
    public Person(String name, int age) {  
        super();  
        this.name = name;  
        this.age = age;  
    }  
    @Override  
    public void writeExternal(ObjectOutput out) throws IOException {  
        // TODO Auto-generated method stub  
        out.writeObject(new StringBuffer(name).reverse());  
        out.writeInt(age);  
    }  
    @Override  
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
        // TODO Auto-generated method stub  
        this.name = ((StringBuffer)in.readObject()).reverse().toString();  
        this.age = in.readInt();  
    }  
      
}  
  • 和Serializable的主要區(qū)別:
    1.效率比Serializable高
    2.但Serializable更加靈活,其最重要的特色就是可以自動序列化,這也是其應(yīng)用更廣泛的原因
    3.使用Externalizable進(jìn)行序列化時,當(dāng)讀取對象時,會調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個新的對象,然后再將被保存對象的字段的值分別填充到新對象中。因此,必須提供一個無參構(gòu)造器,訪問權(quán)限為public;否則會拋出java.io.InvalidClassException 異常;Serializable序列化時不會調(diào)用默認(rèn)的構(gòu)造器
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內(nèi)容

  • 什么是序列化 (1)序列化是將對象轉(zhuǎn)變?yōu)樽止?jié)序列的過程,反序列化則是將字節(jié)序列恢復(fù)為對象的過程。 (2)對象序列化...
    伊凡的一天閱讀 1,623評論 0 4
  • 一、 序列化和反序列化概念 Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程;反序列化de...
    步積閱讀 1,453評論 0 10
  • 序列化和反序列化的概念 序列化:把java對象轉(zhuǎn)換為字節(jié)序列的過程稱為對象的序列化,這些字節(jié)序列可以被保存在磁盤上...
    snoweek閱讀 711評論 0 3
  • JAVA序列化機(jī)制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 10,903評論 0 24
  • 簡介 對于一個存在于Java虛擬機(jī)中的對象來說,其內(nèi)部的狀態(tài)只保持在內(nèi)存中。JVM停止之后,這些狀態(tài)就丟失了。在很...
    FX_SKY閱讀 809評論 0 0