java序列化和反序列話總結

本文轉載自:http://www.cnblogs.com/rollenholt/archive/2012/11/26/2789445.html


序列化:將java對象轉換爲字節序列的過程叫做序列化

反序列化:將字節對象轉換爲java對象的過程叫做反序列化

通常情況下,序列化有兩種用途:、

1) 把對象的字節序列永久的保存在硬盤中

2)在網絡上傳輸對象的字節序列

相應的API

  java.io.ObjectOutputStream

          writeObject(Object obj)

  java.io.ObjectInputStream

          readObject()

只有實現了Serializable或者Externalizable接口的類的對象才能夠被序列化。否則當調用writeObject方法的時候會出現IOException。

需要注意的是Externalizable接口繼承自Serializable接口。兩者的區別如下:

  僅僅實現Serializable接口的類可應採用默認的序列化方式。比如String類。

    假設有一個Customer類的對象需要序列化,如果這個類僅僅實現了這個接口,那麼序列化和反序列化的方式如下:ObjectOutputStream採用默認的序列化方式,對於這個類的非static,非transient的實例變量進行序列化。ObjectInputStream採用默認的反序列化方式,對於這個類的非static,非transient的實例變量進行反序列化。

    如果這個類不僅實現了Serializable接口,而且定義了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那麼將按照如下的方式進行序列化和反序列化:ObjectOutputStream會調用這個類的writeObject方法進行序列化,ObjectInputStream會調用相應的readObject方法進行反序列化。

  實現Externalizable接口的類完全由自身來控制序列化的行爲。而且必須實現writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那麼將按照如下的方式進行序列化和反序列化:ObjectOutputStream會調用這個類的writeExternal方法進行序列化,ObjectInputStream會調用相應的readExternal方法進行反序列化。

下面來看一個最簡單的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.java;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class simpleSerializableTest {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj"));
         
        String strObj="name";
        Customer customer=new Customer("rollen");
        //序列化,此處故意將同一對象序列化2次
        out.writeObject(strObj);
        out.writeObject(customer);
        out.writeObject(customer);
        out.close();
        //反序列化
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj"));
        String strobj1=(String)in.readObject();
        Customer cus1=(Customer)in.readObject();
        Customer cus2=(Customer)in.readObject();<br>            in.close();
        System.out.println(strobj1+": "+cus1);
        System.out.println(strObj==strobj1);
        System.out.println(cus1==customer);
        System.out.println(cus1==cus2);
    }
}
 
class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
 
    public Customer() {
        System.out.println("無參構造方法");
    }
 
    public Customer(String name) {
        System.out.println("有參構造方法");
        this.name = name;
    }
 
    public String toString() {
        return "[ "+name+" ]";
    }
     
}

輸出結果爲:

有參構造方法
name: [ rollen ]
false
false
true

可以看出,在進行反序列話的時候,並沒有調用類的構造方法。而是直接根據他們的序列化數據在內存中創建新的對象。另外需要注意的是,如果由一個ObjectOutputStream對象多次序列化同一個對象,那麼右一個objectInputStream對象反序列化後的也是同一個對象。(cus1==cus2結果爲true可以看出)

看一段代碼,證明static是不會被序列化的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.java;
 
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
 
public class SerializableServer {
    public void send(Object obj) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8000);
        while (true) {
            Socket socket = serverSocket.accept();
            ObjectOutputStream out = new ObjectOutputStream(
                    socket.getOutputStream());
            out.writeObject(obj);
            out.writeObject(obj);
            out.close();
            socket.close();
        }
    }
 
    public static void main(String[] args) throws Exception {
 
        Customer customer = new Customer("rollen""male");
        new SerializableServer().send(customer);
    }
}
 
class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private static int count;
    private transient String sex;
 
    static {
        System.out.println("調用靜態代碼塊");
    }
 
    public Customer() {
        System.out.println("無參構造方法");
    }
 
    public Customer(String name, String sex) {
        System.out.println("有參構造方法");
        this.name = name;
        this.sex = sex;
        count++;
 
    }
 
    public String toString() {
        return "[ " + count + " " + name + " " + sex + " ]";
    }
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.java;
 
import java.io.ObjectInputStream;
import java.net.Socket;
 
public class SerializableClient {
    public void recive() throws Exception {
        Socket socket = new Socket("localhost"8000);
        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
        Object obj1 = in.readObject();
        Object obj2 = in.readObject();
        System.out.println(obj1);
        System.out.println(obj1==obj2);
    }
 
    public static void main(String[] args) {
        try {
            new SerializableClient().recive();
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  運行結果中,count的值爲0.

我們來看另外一種情況:

1
2
3
4
5
6
7
8
class implements Serializable{
    B b;
    //...
}
 
class implements Serializable{
    //...
}

  當我們在序列化A的對象的時候,也會自動序列化和他相關聯的B的對象。也就是說在默認的情況下,對象輸出流會對整個對象圖進行序列化。因此會導致出現下面的問題,看代碼(例子中是使用雙向列表作爲內部結構的,只是給出了demo,並沒有完整的實現,只是爲了說明情況):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriListTest implements Serializable {
    private static final long serialVersionUID = 1L;
    private int size;
    private Node head = null;
    private Node end = null;
 
    private static class Node implements Serializable {
        private static final long serialVersionUID = 1L;
        String data;
        Node next;
        Node previous;
    }
 
    // 列表末尾添加一個字符串
    public void add(String data) {
        Node node = new Node();
        node.data = data;
        node.next = null;
        node.previous = end;
        if (null != end) {
            end.next = node;
        }
        size++;
        end = node;
        if (size == 1) {
            head = end;
        }
    }
 
    public int getSize() {
        return size;
    }
 
    // other methods...
 
    public static void main(String[] args) throws Exception {
        SeriListTest list = new SeriListTest();
        for (int i = 0; i < 10000; ++i) {
            list.add("rollen" + i);
        }
 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(list);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        list = (SeriListTest) in.readObject();
        System.out.println("size is :" + list.getSize());
    }
 
}

  這段代碼會出現如下錯誤:

Exception in thread "main" java.lang.StackOverflowError
  at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
  at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
      ....

整個就是因爲序列化的時候,對整個對象圖進行序列化引起的問題。在這種情況下啊,我們需要自定義序列化的過程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriListTest implements Serializable {
    private static final long serialVersionUID = 1L;
    transient private int size;
    transient private Node head = null;
    transient private Node end = null;
 
    private static class Node implements Serializable {
        private static final long serialVersionUID = 1L;
        String data;
        Node next;
        Node previous;
    }
 
    // 列表末尾添加一個字符串
    public void add(String data) {
        Node node = new Node();
        node.data = data;
        node.next = null;
        node.previous = end;
        if (null != end) {
            end.next = node;
        }
        size++;
        end = node;
        if (size == 1) {
            head = end;
        }
    }
 
    public int getSize() {
        return size;
    }
 
    // other methods...
 
    private void writeObject(ObjectOutputStream outStream) throws IOException {
        outStream.defaultWriteObject();
        outStream.writeInt(size);
        for (Node node = head; node != null; node = node.next) {
            outStream.writeObject(node.data);
        }
    }
 
    private void readObject(ObjectInputStream inStream) throws IOException,
            ClassNotFoundException {
        inStream.defaultReadObject();
        int count = inStream.readInt();
        for (int i = 0; i < count; ++i) {
            add((String) inStream.readObject());
        }
    }
 
    public static void main(String[] args) throws Exception {
        SeriListTest list = new SeriListTest();
        for (int i = 0; i < 10000; ++i) {
            list.add("rollen" + i);
        }
 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(list);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        list = (SeriListTest) in.readObject();
        System.out.println("size is :" + list.getSize());
    }
 
}

  運行結果爲:10000

現在我們總結一下,在什麼情況下我們需要自定義序列化的方式:

  1)爲了確保序列化的安全性,對於一些敏感信息加密

  2)確保對象的成員變量符合正確的約束條件

  3)優化序列化的性能(之前的那個例子已經解釋了這種情況)

下面我們來用例子解釋一下這些:

先來看看:爲了確保序列化的安全性,對於一些敏感信息加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriDemo1 implements Serializable {
    private String name;
    transient private String password; // 注意此處的transient
 
    public SeriDemo1() {
    }
 
    public SeriDemo1(String name, String password) {
        this.name = name;
        this.password = password;
    }
 
    // 此處模擬對密碼進行加密,進行了簡化
    private String change(String password) {
        return password + "rollen";
    }
 
    private void writeObject(ObjectOutputStream outStream) throws IOException {
        outStream.defaultWriteObject();
        outStream.writeObject(change(password));
    }
 
    private void readObject(ObjectInputStream inStream) throws IOException,
            ClassNotFoundException {
        inStream.defaultReadObject();
        String strPassowrd = (String) inStream.readObject();
        //此處模擬對密碼解密
        password = strPassowrd.substring(0, strPassowrd.length() - 6);
    }
 
    @Override
    public String toString() {
        return "SeriDemo1 [name=" + name + ", password=" + password + "]";
    }
 
    public static void main(String[] args) throws Exception {
        SeriDemo1 demo = new SeriDemo1("hello""1234");
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        demo = (SeriDemo1) in.readObject();
        System.out.println(demo);
    }
}

  然後我們看看:確保對象的成員變量符合正確的約束條件。比如一般情況下我們會在構造函數中對於參數進行合法性檢查,但是默認的序列化並不會調用類的構造函數,直接由對象的序列化數據來構造出一個對象,這個我們就有可能提供遺傳非法的序列化數據,來構造一個不滿足約束條件的對象。

爲了避免這種情況,我們可以自定義反序列話的方式。比如在readObject方法中,進行檢查。當數據不滿足約束的時候(比如年齡小於0等等不滿足約束的情況),可以拋出異常之類的。

接下來我們看看readResolve()方法在單例模式中的使用:

單例模式大家應該都清楚,我就不多說了,看看下面的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ReadResolveDemo implements Serializable {
    private static final long serialVersionUID = 1L;
 
    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return new ReadResolveDemo();
    }
    public static void main(String[] args) throws Exception {
        ReadResolveDemo demo=ReadResolveDemo.getInstance();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
        System.out.println(demo==demo1); //false
    }
}

  本來單例模式中,只有一個實例,但是在序列化的時候,無論採用默認的方式,還是自定義的方式,在反序列化的時候都會產生一個新的對象,所以上面的程序運行輸出false。

因此可以看出反序列化打破了單例模式只有一個實例的約定,爲了避免這種情況,我們可以使用readReslove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ReadResolveDemo implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();
 
    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return INSTANCE;
    }
 
    private Object readResolve() {
        return INSTANCE;
    }
 
    public static void main(String[] args) throws Exception {
        ReadResolveDemo demo = ReadResolveDemo.getInstance();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
        System.out.println(demo == demo1); // true
    }
}

  最後我們簡單的說一下實現Externalizable接口。 實現Externalizable接口的類完全由自身來控制序列化的行爲。而且必須實現writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。

注意在對實現了這個接口的對象進行反序列化的時候,會先調用類的不帶參數的構造函數,這個和之前的默認反序列化方式是不一樣的。

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
 
public class ExternalizableDemo implements Externalizable {
    private String name;
    static {
        System.out.println("調用靜態代碼塊");
    }
 
    public ExternalizableDemo() {
        System.out.println("調用默認無參構造函數");
    }
 
    public ExternalizableDemo(String name) {
        this.name = name;
        System.out.println("調用有參構造函數");
    }
 
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
    }
 
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        name = (String) in.readObject();
    }
 
    @Override
    public String toString() {
        return "[" + name + "]";
    }
 
    public static void main(String[] args) throws Exception {
        ExternalizableDemo demo = new ExternalizableDemo("rollen");
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        demo = (ExternalizableDemo) in.readObject();
        System.out.println(demo);
    }
}

  輸出:

調用靜態代碼塊
調用有參構造函數
調用默認無參構造函數
[rollen]


 參考資料:

1.java序列化高級認識




發佈了12 篇原創文章 · 獲贊 21 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章