本文轉載自: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 A implements Serializable{ B
b; //... } class B 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]