序列化:将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);
}
}
|
输出:
调用静态代码块
调用有参构造函数
调用默认无参构造函数