java序列化與反序列化

序列化的意義

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

基本知識

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

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

一些雜事

1.將統(tǒng)一個(gè)對象連續(xù)多次寫入一個(gè)文件怜姿,Java 序列化機(jī)制為了節(jié)省磁盤空間沧卢,具有特定的存儲規(guī)則,當(dāng)寫入文件的為同一對象時(shí)披诗,并不會再將對象的內(nèi)容進(jìn)行存儲藤巢,而只是再次存儲一份引用掂咒,上面增加的 5 字節(jié)的存儲空間就是新增引用和一些控制信息的空間迈喉。反序列化時(shí)挨摸,恢復(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"));
    //從文件依次讀出兩個(gè)文件
    Test t1 = (Test) oin.readObject();
    Test t2 = (Test) oin.readObject();
    oin.close();
            
    //判斷兩個(gè)引用是否指向同一個(gè)對象
    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實(shí)際上是動態(tài)數(shù)組课舍,每次在放滿以后自動增長設(shè)定的長度值,如果數(shù)組自動增長長度設(shè)為100雨让,而實(shí)際只放了一個(gè)元素,那就會序列化99個(gè)null元素崔挖。為了保證在序列化的時(shí)候不會將這么多null同時(shí)進(jìn)行序列化狸相,ArrayList把元素?cái)?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的幾個(gè)自定義神器方法

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

  • 方法定義:private void readObjectNoData() throws ObjectStreamException
  • 這個(gè)方法可能會讓你摸不著頭腦吃谣,測試的時(shí)候怎么都不會被執(zhí)行岗憋?澜驮,哈哈哈惋鸥!這里是真相:如果有一個(gè)類A正在被反序列化,S是它現(xiàn)在的父類飞蚓,readObjectNoData方法也應(yīng)該存在與S類中趴拧。當(dāng)發(fā)生以下條件時(shí)著榴,將觸發(fā)這個(gè)readObjectNoData方法被執(zhí)行:
    1.S類直接或間接實(shí)現(xiàn)了Serializable接口
    2.S類必須定義實(shí)現(xiàn)readObjectNoData方法
    3.當(dāng)前待被反序列化的stream在被序列化時(shí)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问麸,如果實(shí)現(xiàn)了writeReplace方法后严卖,那么在序列化時(shí)會先調(diào)用writeReplace方法將當(dāng)前對象替換成另一個(gè)對象(該方法會返回替換后的對象)并將其寫入流中布轿,例如:

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;
    }
    
}

在這里就將該對象直接替換成了一個(gè)list保存稠肘;

  • 實(shí)現(xiàn)writeReplace就不要實(shí)現(xiàn)writeObject了启具,實(shí)現(xiàn)了也不會被調(diào)用;
  • writeReplace的返回值(對象)必須是可序列話的鲁冯,如果是Java自己的基礎(chǔ)類或者類型那就不用說了薯演;
  • writeReplace替換后在反序列化時(shí)就無法被恢復(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替換寫入后也不能通過實(shí)現(xiàn)readObject來實(shí)現(xiàn)自動恢復(fù)了,即下面的代碼是錯(cuò)誤的谦絮!因?yàn)槟J(rèn)已經(jīng)被徹底替換了须误,就不存在自定義反序列化的問題了京痢,直接自動反序列化成ArrayList了,該方法即使實(shí)現(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 { // 錯(cuò)方淤!寫了也沒用
        ArrayList<Object> list = (ArrayList)in.readObject();
        this.name = (String)list.get(0);
        this.age = (int)list.get(1);
    }
}

所以,writeObject只和readObject配合使用蹄殃,一旦實(shí)現(xiàn)了writeReplace在寫入時(shí)進(jìn)行替換就不再需要writeObject和readObject了携茂!因?yàn)樘鎿Q就已經(jīng)是徹底的自定義了,比writeObject/readObject更徹底诅岩!

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

  • readResolve會在readObject調(diào)用之后自動調(diào)用讳苦,它最主要的目的就是讓恢復(fù)的對象變個(gè)樣,比如readObject已經(jīng)反序列化好了一個(gè)Person對象吩谦,那么就可以在readResolve里再對該對象進(jìn)行一定的修改鸳谜,而最終修改后的結(jié)果將作為ObjectInputStream的readObject的返回結(jié)果;
  • 可以返回一個(gè)非原類型的對象式廷,也就是說可以徹底替換對象
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 { // 直接替換成一個(gè)int的1返回
        return 1;
    }
}
  • 其最重要的應(yīng)用就是保護(hù)性恢復(fù)單例蝗肪、枚舉類型的對象!

枚舉問題示例:

class Brand implements Serializable {  
    private int val;  
    private Brand(int val) {  
        this.val = val;  
    }  
      
    // 兩個(gè)枚舉值  
    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  !!!!!!!!!因?yàn)锽rand.NIKE是程序中創(chuàng)建的對象,而b是從磁盤中讀取并恢復(fù)過來的對象,兩者明顯來源不同,因此必然內(nèi)存空間是不同的,引用(地址)顯然也是不同的
        }  
    }  
}  

使用readResolve解決這個(gè)問題:

class Brand implements Serializable {  
    private int val;  
    private Brand(int val) {  
        this.val = val;  
    }  
      
    // 兩個(gè)枚舉值  
    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)制自己實(shí)現(xiàn)串行化和反串行化——Externalizable接口

  • 不像Serializable接口只是一個(gè)標(biāo)記接口么库,里面的接口方法都是可選的(可實(shí)現(xiàn)可不實(shí)現(xiàn),如果不實(shí)現(xiàn)則啟用其自動序列化功能),而Externalizable接口不是一個(gè)標(biāo)記接口胁澳,它強(qiáng)制你自己動手實(shí)現(xiàn)串行化和反串行化算法:
    
 //可以看到該接口直接繼承了Serializable接口,其兩個(gè)方法其實(shí)就對應(yīng)了Serializable的writeObject和readObject方法,實(shí)現(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)制實(shí)現(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)行序列化時(shí),當(dāng)讀取對象時(shí),會調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個(gè)新的對象,然后再將被保存對象的字段的值分別填充到新對象中。因此,必須提供一個(gè)無參構(gòu)造器,訪問權(quán)限為public;否則會拋出java.io.InvalidClassException 異常;Serializable序列化時(shí)不會調(diào)用默認(rèn)的構(gòu)造器
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末不皆,一起剝皮案震驚了整個(gè)濱河市犬耻,隨后出現(xiàn)的幾起案子计济,更是在濱河造成了極大的恐慌,老刑警劉巖漩氨,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抡草,死亡現(xiàn)場離奇詭異,居然都是意外死亡钝诚,警方通過查閱死者的電腦和手機(jī)尚辑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門决左,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婴渡,“玉大人,你說我怎么就攤上這事媒峡。” “怎么了糯崎?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長流强。 經(jīng)常有香客問我,道長第股,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任控漠,我火速辦了婚禮,結(jié)果婚禮上习霹,老公的妹妹穿的比我還像新娘淋叶。我一直安慰自己斟湃,他們只是感情好墓猎,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般房揭。 火紅的嫁衣襯著肌膚如雪备闲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天捅暴,我揣著相機(jī)與錄音恬砂,去河邊找鬼。 笑死蓬痒,一個(gè)胖子當(dāng)著我的面吹牛泻骤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乳幸,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼瞪讼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粹断?” 一聲冷哼從身側(cè)響起符欠,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瓶埋,沒想到半個(gè)月后希柿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡养筒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年曾撤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晕粪。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挤悉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巫湘,到底是詐尸還是另有隱情装悲,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布尚氛,位于F島的核電站诀诊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏阅嘶。R本人自食惡果不足惜属瓣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一载迄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抡蛙,春花似錦护昧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽极祸。三九已至慈格,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遥金,已是汗流浹背浴捆。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稿械,地道東北人选泻。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像美莫,于是被迫代替她去往敵國和親页眯。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

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

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