JAVA 對象序列化——Serializable

  • Java的對象序列化是指將那些實現(xiàn)了Serializable接口的對象轉(zhuǎn)換成一個字符序列,并能夠在以后將這個字節(jié)序列完全恢復(fù)為原來的對象。這一過程甚至可通過網(wǎng)絡(luò)進(jìn)行,這意味著序列化機(jī)制能自動彌補(bǔ)不同操作系統(tǒng)之間的差異。 只要對象實現(xiàn)了Serializable接口(記住,這個接口只是一個標(biāo)記接口壶谒,不包含任何的方法

  • 如果我們想要序列化一個對象,首先要創(chuàng)建某些OutputStream(如FileOutputStream膳沽、ByteArrayOutputStream等)汗菜,然后將這些OutputStream封裝在一個ObjectOutputStream中。這時候贵少,只需要調(diào)用writeObject()方法就可以將對象序列化呵俏,并將其發(fā)送給OutputStream(記住:對象的序列化是基于字節(jié)的滔灶,不能使用Reader和Writer等基于字符的層次結(jié)構(gòu))普碎。而反序列的過程(即將一個序列還原成為一個對象),需要將一個InputStream(如FileInputstream录平、ByteArrayInputStream等)封裝在ObjectInputStream內(nèi)麻车,然后調(diào)用readObject()即可。

  • 對象序列化過程不僅僅保存單個對象斗这,還能追蹤對象內(nèi)所包含的所有引用动猬,并保存那些對象(這些對象也需實現(xiàn)了Serializable接口)。下面這段代碼演示了此過程:

package test.serializable;

/**
 *只是一個簡單的類表箭,用于測試序列化
 */
import java.io.Serializable;

public class Data implements Serializable {
    private static final long serialVersionUID = 7247714666080613254L;
    public int n;
    public Data(int n) {
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}
package test.serializable;

import java.io.Serializable;
import java.util.Random;

/**
 * 
 * @author chenfei
 *
 * 用于測試序列化赁咙,每個對象Worm對象都與worm中的下一段鏈接,
 * 同時又有屬于不同類(Data)的對象引用數(shù)組鏈接
 */
public class Worm implements Serializable {
    private static final long serialVersionUID = 5468335797443850679L;
    private Data[] d = {
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10)),
            new Data(random.nextInt(10))
    };
    private static Random random = new Random(47);
    private Worm next;
    private char c;
    
    public Worm(int i , char x) {
        System.out.println("Worm constructor:" +i);
        c = x;
        if(--i > 0) {
            next = new Worm(i , (char)(x+1));
        }
    }
    public Worm() {
        System.out.println("Default constructor!");
    }
    
    public String toString() {
        StringBuilder sb = new StringBuilder(":");
        sb.append(c);
        sb.append("(");
        for(Data data : d) {
            sb.append(data);
        }
        sb.append(")");
        if(next!=null) {
            sb.append(next);
        }
        return sb.toString();
    }
}
package test.serializable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class SerializableTest {
    
    public static void main(String[] args) throws FileNotFoundException,
        IOException, ClassNotFoundException {
        Worm w = new Worm(6 ,'a');
        System.out.println("序列化操縱之前");
        System.out.println("w="+w);
        
        //序列化操作1--FileOutputStream
        ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("worm.out"));
        oos1.writeObject("Worm storage By FileOutputStream ");
        oos1.writeObject(w);//必須所有引用的對象都實現(xiàn)序列化(本例終究是Data這個類)
        //否則拋出有java.io.NotSerializableException:這個異常
        oos1.close();
        
        //反序列化操作1---FileInputStream
        ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("worm.out"));
        String s1 = (String)ois1.readObject();
        Worm w1 = (Worm)ois1.readObject();
        ois1.close();
        System.out.println("反序列化操作1之后");
        System.out.println(s1);
        System.out.println("w1:"+w1);
        
        //序列化操作2--ByteArrayOutputStream
        ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
        ObjectOutputStream oos2 = new ObjectOutputStream(byteOutStream);
        oos2.writeObject("Worm storage By ByteOutputStream ");
        oos2.writeObject(w);
        oos2.flush();
        
        //反序列操作2--ByteArrayInputStream
        ByteArrayInputStream byteInStream = new ByteArrayInputStream(byteOutStream.toByteArray());
        ObjectInputStream ois2 = new ObjectInputStream(byteInStream);
        String s2 = (String)ois2.readObject();
        Worm w2 = (Worm)ois2.readObject();
        ois2.close();
        System.out.println("反序列化操作2之后");
        System.out.println(s2);
        System.out.println("w2:"+w2);
    }
}

運(yùn)行結(jié)果如下:

Worm constructor:6
Worm constructor:5
Worm constructor:4
Worm constructor:3
Worm constructor:2
Worm constructor:1
序列化操縱之前
w=:a(853):b(119):c(802):d(788):e(199):f(881)
反序列化操作1之后
Worm storage By FileOutputStream 
w1::a(853):b(119):c(802):d(788):e(199):f(881)
反序列化操作2之后
Worm storage By ByteOutputStream 
w2::a(853):b(119):c(802):d(788):e(199):f(881)

思考:

1)反序列化后的對象免钻,需要調(diào)用構(gòu)造函數(shù)重新構(gòu)造嗎彼水?
 答案:不需要。對于Serializable對象极舔,對象完全以它存儲的二進(jìn)制位作為基礎(chǔ)來構(gòu)造凤覆,而不調(diào)用構(gòu)造器

請看下面這段代碼

package test.serializable;

import java.io.Serializable;
import java.util.Date;

/**
 * 
 * @author chenfei
 * 
 * 用于測試序列化時的deep copy
 *
 */
public class House implements Serializable {
    private static final long serialVersionUID = -6091530420906090649L;
    
    private Date date = new Date(); //記錄當(dāng)前的時間
    
    public String toString() {
        return "House:" + super.toString() + ".Create Time is:" + date;
    }

}
package test.serializable;

import java.io.Serializable;

public class Animal implements Serializable {
    private static final long serialVersionUID = -213221189192962074L;
    
    private String name;
    
    private House house;
    
    public Animal(String name , House house) {
        this.name = name;
        this.house = house;
        System.out.println("調(diào)用了構(gòu)造器");
    }
    
    public String toString() {
        return  name + "[" +super.toString() + "']" + house;
    }

}
package test.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class Myworld {

    /**
     * @param args
     * @throws IOException 
     * @throws ClassNotFoundException 
     */
public static void main(String[] args) throws IOException, ClassNotFoundException {
    House house = new House();
    System.out.println("序列化前");
    Animal animal = new Animal("test",house);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(out);
    oos.writeObject(animal);
    oos.flush();
    oos.close();

    System.out.println("反序列化后");
    ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(in);
    Animal animal1 = (Animal)ois.readObject();
    ois.close();    
    }

}

運(yùn)行結(jié)果如下所示:

序列化前
調(diào)用了構(gòu)造器
反序列化后
從上面的結(jié)果中可以看到,在序列化前拆魏,當(dāng)我們使用

Animal animal = new Animal("test",house);
時盯桦,調(diào)用了Animal的構(gòu)造器(打印了輸出語句)慈俯,但是反序列后并沒有再打印任何語句,說明并沒有調(diào)用構(gòu)造器拥峦。

2)序列前的對象與序列化后的對象是什么關(guān)系贴膘?是("=="還是equal?是淺復(fù)制還是深復(fù)制事镣?)
 答案:深復(fù)制步鉴,反序列化還原后的對象地址與原來的的地址不同。 我們還是看上面思考1)中給出的代碼璃哟,
           前兩個類不變化,修改第三個類(MyWorld.java)的部分代碼

修改后的代碼如下:

package test.serializable;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class Myworld {

    /**
     * @param args
     * @throws IOException 
     * @throws ClassNotFoundException 
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        House house = new House();
        System.out.println("序列化前");
        Animal animal = new Animal("test",house);
        System.out.println(animal);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(animal);
        oos.writeObject(animal);//在寫一次喊递,看對象是否是一樣随闪,
        oos.flush();
        oos.close();
        
        ByteArrayOutputStream out2 = new ByteArrayOutputStream();//換一個輸出流
        ObjectOutputStream oos2 = new ObjectOutputStream(out2);
        oos2.writeObject(animal);
        oos2.flush();
        oos2.close();

        System.out.println("反序列化后");
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        Animal animal1 = (Animal)ois.readObject();
        Animal animal2 = (Animal)ois.readObject();
        ois.close();
        
        ByteArrayInputStream in2 = new ByteArrayInputStream(out2.toByteArray());
        ObjectInputStream ois2 = new ObjectInputStream(in2);
        Animal animal3 = (Animal)ois2.readObject();
        ois2.close();
        
        System.out.println("out流:" +animal1);
        System.out.println("out流:" +animal2);
        System.out.println("out2流:" +animal3);
        
        
        System.out.println("測試序列化前后的對象 == :"+ (animal==animal1));
        System.out.println("測試序列化后同一流的對象:"+ (animal1 == animal2));
        System.out.println("測試序列化后不同流的對象==:" + (animal1==animal3));
        
    }

}

運(yùn)行結(jié)果如下:

序列化前
調(diào)用了構(gòu)造器
test[test.serializable.Animal@bb7465']House:test.serializable.House@d6c16c.Create Time is:Sat Apr 06 00:11:30 CST 2013
反序列化后
out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013
out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013
(與上面的相同)
out2流:test[test.serializable.Animal@12cc95d']House:test.serializable.House@157fb52.Create Time is:Sat Apr 06 00:11:30 CST 2013
(與上面只是值相同,但是地址不一樣骚勘。)
測試序列化前后的對象 == :false
測試序列化后同一流的對象:true
測試序列化后不同流的對象==:false

從結(jié)果可以看到

序列化前后對象的地址不同了铐伴,但是內(nèi)容是一樣的,而且對象中包含的引用也相同俏讹。換句話說当宴,通過序列化操作, 我們可以實現(xiàn)對任何可Serializable對象的”深度復(fù)制(deep copy)

——這意味著我們復(fù)制的是整個對象網(wǎng),而不僅僅是基本對象及其引用泽疆。對于同一流的對象户矢,他們地址是相同,說明他們是同一個對象殉疼,但是與其他流的對象地址卻不相同梯浪。
也就說,只要將對象序列化到單一流中瓢娜,就可以恢復(fù)出與我們寫出時一樣的對象網(wǎng)挂洛,而且只要在同一流中,對象都是同一個眠砾。

補(bǔ)充:

serialVersionUID 的作用虏劲?
  • 在Java中,軟件的兼容性是一個大問題褒颈,尤其在使用到對象串行性的時候柒巫,那么在某一個對象已經(jīng)被串行化了,可是這個對象又被修改后重新部署了哈肖,那么在這種情況下吻育, 用老軟件來讀取新文件格式雖然不是什么難事,但是有可能丟失一些信息淤井。 serialVersionUID來解決這些問題布疼,新增的serialVersionUID必須定義成下面這種形式:static final long serialVersionUID=-2805284943658356093L;摊趾。其中數(shù)字后面加上的L表示這是一個long值。 通過這種方式來解決不同的版本之間的串行話問題游两。

Java串行化機(jī)制定義的文件格式似乎很脆弱砾层,只要稍微改動一下類的定義,原來保存的對象就可能無法讀取贱案。例如肛炮,下面是一個簡單的類定義:

public class Save implements Serializable
{
String name;

public void save() throws IOException
{
FileOutputStream f = new FileOutputStream("foo");
ObjectOutputStream oos = new ObjectOutputStream(f);
oos.writeObject(this);
oos.close();
}
}

如果在這個類定義中增加一個域,例如final int val = 7;宝踪,再來讀取原來保存的對象侨糟,就會出現(xiàn)下面的異常

  java.io.InvalidClassException:
Save; local class incompatible:
stream classdesc serialVersionUID = -2805284943658356093,
local class serialVersionUID = 3419534311899376629

上例異常信息中的數(shù)字串表示類定義里各種屬性的編碼值:

  • 類的名字(Save)。

  • 域的名字(name)瘩燥。

  • 方法的名字(Save)秕重。

  • 已實現(xiàn)的接口(Serializable)。

改動上述任意一項內(nèi)容(無論是增加或刪除)厉膀,都會引起編碼值變化溶耘,從而引起類似的異常警報。這個數(shù)字序列稱為“串行化版本統(tǒng)一標(biāo)識符”(serial version universal identifier)服鹅,簡稱UID凳兵。解決這個問題的辦法是在類里面新增一個域serialVersionUID,強(qiáng)制類仍舊使用原來的UID企软。新增的域必須是:

  • static:該域定義的屬性作用于整個類庐扫,而非特定的對象。

  • final:保證代碼運(yùn)行期間該域不會被修改澜倦。

  • long:它是一個64位的數(shù)值聚蝶。

也就是說,新增的serialVersionUID必須定義成下面這種形式:static final long serialVersionUID=-2805284943658356093L;藻治。其中數(shù)字后面加上的L表示這是一個long值碘勉。

當(dāng)然,改動之后的類不一定能夠和原來的對象兼容桩卵。例如验靡,如果把一個域的定義從String改成了int,執(zhí)行逆-串行化操作時系統(tǒng)就不知道如何處理該值雏节,顯示出錯誤信息:java.io.InvalidClassException: Save; incompatible types for field name胜嗓。

PS:
serialVersionUID 的作用
介紹詳解:http://www.360doc.com/content/09/1019/17/397210_7519829.shtml
原文地址:http://www.cnblogs.com/chenfei0801/archive/2013/04/05/3001149.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钩乍,隨后出現(xiàn)的幾起案子辞州,更是在濱河造成了極大的恐慌,老刑警劉巖寥粹,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件变过,死亡現(xiàn)場離奇詭異埃元,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)媚狰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門岛杀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人崭孤,你說我怎么就攤上這事类嗤。” “怎么了辨宠?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵遗锣,是天一觀的道長。 經(jīng)常有香客問我彭羹,道長黄伊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任派殷,我火速辦了婚禮,結(jié)果婚禮上墓阀,老公的妹妹穿的比我還像新娘毡惜。我一直安慰自己,他們只是感情好斯撮,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布经伙。 她就那樣靜靜地躺著,像睡著了一般勿锅。 火紅的嫁衣襯著肌膚如雪帕膜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天溢十,我揣著相機(jī)與錄音垮刹,去河邊找鬼。 笑死张弛,一個胖子當(dāng)著我的面吹牛荒典,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吞鸭,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼寺董,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刻剥?” 一聲冷哼從身側(cè)響起遮咖,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎造虏,沒想到半個月后御吞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體麦箍,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年魄藕,在試婚紗的時候發(fā)現(xiàn)自己被綠了内列。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡背率,死狀恐怖话瞧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寝姿,我是刑警寧澤交排,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饵筑,受9級特大地震影響埃篓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜根资,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一架专、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧玄帕,春花似錦部脚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹰椒,卻和暖如春锡移,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漆际。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工淆珊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灿椅。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓套蒂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茫蛹。 傳聞我的和親對象是個殘疾皇子操刀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

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