JAVA 對象序列化(一)——Serializable

  Java的對象序列化是指將那些實現了Serializable接口的對象轉換成一個字符序列,並能夠在以後將這個字節序列完全恢復爲原來的對象。這一過程甚至可通過網絡進行,這意味着序列化機制能自動彌補不同操作系統之間的差異。 只要對象實現了Serializable接口(記住,這個接口只是一個標記接口,不包含任何的方法

  如果我們想要序列化一個對象,首先要創建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然後將這些OutputStream封裝在一個ObjectOutputStream中。這時候,只需要調用writeObject()方法就可以將對象序列化,並將其發送給OutputStream(記住:對象的序列化是基於字節的,不能使用Reader和Writer等基於字符的層次結構)。而飯序列的過程(即將一個序列還原成爲一個對象),需要將一個InputStream(如FileInputstream、ByteArrayInputStream等)封裝在ObjectInputStream內,然後調用readObject()即可。
  對象序列化過程不僅僅保存單個對象,還能追蹤對象內所包含的所有引用,並保存那些對象(這些對象也需實現了Serializable接口)。下面這段代碼演示了此過程:


package test.serializable;

/**
 *@chenfeic 
 *
 *只是一個簡單的類,用於測試序列化
 */
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)的對象引用數組鏈接
 */
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);//必須所有引用的對象都實現序列化(本例終究是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);
    }


}

運行的結果如下:


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)反序列化後的對象,需要調用構造函數重新構造嗎?

       答案:不需要。對於Serializable對象,對象完全以它存儲的二進制位作爲基礎來構造,而不調用構造器。

     請看下面這段代碼

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(); //記錄當前的時間

    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("調用了構造器");
    }

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

}
運行結果如下所示:

序列化前
調用了構造器
反序列化後

從上面的結果中可以看到,在序列化前,當我們使用

    Animal animal = new Animal("test",house);

時,調用了Animal的構造器(打印了輸出語句),但是反序列後並沒有再打印任何語句,說明並沒有調用構造器。

2)序列前的對象與序列化後的對象是什麼關係?是(“==”還是equal?是淺複製還是深複製?)

       答案:深複製,反序列化還原後的對象地址與原來的的地址不同。 我們還是看上面思考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));

    }

}
  運行結果如下:

序列化前
調用了構造器
test[test.serializable.Animal@bb7465']House:[email protected] 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:[email protected] 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

從結果可以看到

    序列化前後對象的地址不同了,但是內容是一樣的,而且對象中包含的引用也相同。換句話說,通過序列化操作,我們可以實現對任何可Serializable對象的”深度複製(deep copy)"——這意味着我們複製的是整個對象網,而不僅僅是基本對象及其引用。對於同一流的對象,他們的地址是相同,說明他們是同一個對象,但是與其他流的對象地址卻不相同。也就說,只要將對象序列化到單一流中,就可以恢復出與我們寫出時一樣的對象網,而且只要在同一流中,對象都是同一個。

補充:

 serialVersionUID 的作用?

     在Java中,軟件的兼容性是一個大問題,尤其在使用到對象串行性的時候,那麼在某一個對象已經被串行化了,可是這個對象又被修改後重新部署了,那麼在這種情況下, 用老軟件來讀取新文件格式雖然不是什麼難事,但是有可能丟失一些信息。 serialVersionUID來解決這些問題,新增的serialVersionUID必須定義成下面這種形式:static final long serialVersionUID=-2805284943658356093L;。其中數字後面加上的L表示這是一個long值。 通過這種方式來解決不同的版本之間的串行話問題。

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

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;,再來讀取原來保存的對象,就會出現下面的異常:

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

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

●類的名字(Save)。

●域的名字(name)。

●方法的名字(Save)。

●已實現的接口(Serializable)。

改動上述任意一項內容(無論是增加或刪除),都會引起編碼值變化,從而引起類似的異常警報。這個數字序列稱爲“串行化版本統一標識符”(serial version universal identifier),簡稱UID。解決這個問題的辦法是在類裏面新增一個域serialVersionUID,強制類仍舊使用原來的UID。新增的域必須是:

●static:該域定義的屬性作用於整個類,而非特定的對象。

●final:保證代碼運行期間該域不會被修改。

●long:它是一個64位的數值。

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

當然,改動之後的類不一定能夠和原來的對象兼容。例如,如果把一個域的定義從String改成了int,執行逆-串行化操作時系統就不知道如何處理該值,顯示出錯誤信息:java.io.InvalidClassException: Save; incompatible types for field name。

下面這篇文章比較詳細的介紹了serialVersionUID 的作用

http://www.360doc.com/content/09/1019/17/397210_7519829.shtml

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章