Java序列化与反序列化基础

0x01 概述

什么是序列化,简单的来说,序列化就是为了保存对象的状态;而反序列化就是把保存的对象状态再读出来。

使用场景:

  1. 当想把内存中的对象状态保存到一个文件或者数据库中的时候
  2. 当想用套接字在网络上传送对象的时候
  3. 当想通过RMI传输对象的时候

0x02 Java支持序列化种类

Java支持的序列化有三种

  1. 自定义实现Serializable接口的类
  2. Java的基本类型
  3. Java自带的实现了Serializable接口的类

下面用程序展示着三种情况

支持自定义实现Serializable接口的类:

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
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class SerialTest1 {

private static final String TMP_FILE = ".serialtest1.txt";

public static void main (String args[]) {

testWrite();
testRead();
}

private static void testWrite() {

try {

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(TMP_FILE));
Box box = new Box("desk", 80, 48);
out.writeObject(box);
System.out.println("testWrite box:" + box);
out.close();
}catch (Exception ex) {

ex.printStackTrace();
}
}

private static void testRead() {

try {

ObjectInputStream in = new ObjectInputStream(new FileInputStream(TMP_FILE));
Box box = (Box)in.readObject();
System.out.println("testRead box:" + box);
in.close();
}catch (Exception e) {

e.printStackTrace();
}
}
}

class Box implements Serializable {

private int width;
private int height;
private String name;

public Box (String name, int width, int height) {

this.name = name;
this.height = height;
this.width = width;
}

public String toString () {
return "[" + name + ": (" + width + ", " + height + ") ]";
}
}

运行结果:

支持java的基本类型和自带的实现了Serializable接口的类

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
import java.io.FileInputStream;   
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

public class SerialTest2 {
private static final String TMP_FILE = ".serialabletest2.txt";

public static void main(String[] args) {
testWrite();
testRead();
}


private static void testWrite() {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(TMP_FILE));
out.writeBoolean(true);
out.writeByte((byte)65);
out.writeChar('a');
out.writeInt(20131015);
out.writeFloat(3.14F);
out.writeDouble(1.414D);

HashMap map = new HashMap();
map.put("one", "red");
map.put("two", "green");
map.put("three", "blue");
out.writeObject(map);

out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}


private static void testRead() {
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream(TMP_FILE));
System.out.printf("boolean:%b\n" , in.readBoolean());
System.out.printf("byte:%d\n" , (in.readByte()&0xff));
System.out.printf("char:%c\n" , in.readChar());
System.out.printf("int:%d\n" , in.readInt());
System.out.printf("float:%f\n" , in.readFloat());
System.out.printf("double:%f\n" , in.readDouble());

HashMap map = (HashMap) in.readObject();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
System.out.printf("%-6s -- %s\n" , entry.getKey(), entry.getValue());
}

in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果

这里HashMap是java.util包中定义的类,它属于java自带的实现Serializable接口的类,它的接口声明如下:

1
2
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}

0x03 序列化中的特例

从上面说得,我们知道序列化/反序列化,只支持保存/恢复对象状态,即仅支持保存/恢复类的成员变量,但不支持保存类的成员方法,但是,序列化是不是对类的所有的成员变量的状态都能保存呢?答案是否定的。

  1. 序列化对static和transient变量,是不会自动进行状态保存的。
    transient的作用就是,用transient声明的变量,不会被自动序列化。
    
  2. 对于Socket, Thread类,不支持序列化。若实现序列化的接口中,有Thread成员;在对该类进行序列化操作时,编译会出错!

static与transient
首先说一下序列化对static和transient的处理吧,我们将之前的代码中Box类修改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Box implements Serializable {
private static int width;
private transient int height;
private String name;

public Box(String name, int width, int height) {
this.name = name;
this.width = width;
this.height = height;
}

public String toString() {
return "["+name+": ("+width+", "+height+") ]";
}
}

将成员变量的类型修改为static和transient,运行一下,结果:

前面说得,序列化不对static和transient变量进行状态保存的。因此,testWrite()中保存Box对象时,不 会保存width和height的值。但是为什么testRead()读出来的Box对象中width=80,而height=0呢?

对于height,因为Box对象中height是int类型,而int类型默认是0,因此height为0.

而对于width,它是static类型,而static类型意味着所有Box对象都公用一个heith值,而在testWrite()中,我们已经将其初始化为80,因此,我们通过序列化读出来width也是80.

那么,如果我们想要保存static或transient变量,也是可以的,只要重写两个方法writeObject()和readObject()即可。
还是Box类

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
class Box implements Serializable {
private static int width;
private transient int height;
private String name;

public Box(String name, int width, int height) {
this.name = name;
this.width = width;
this.height = height;
}

private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeInt(height);
out.writeInt(width);
}

private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject();
height = in.readInt();
width = in.readInt();
}

public String toString() {
return "["+name+": ("+width+", "+height+") ]";
}
}

在writeObject()方法中,out.defaultWriteObject()是使定制的writeObject()方法可以利用自动序列化中内置的逻辑

在readObject()方法中,in.defaultReadObject()也是使定制的readObject()方法可以利用自动序列化中内置的逻辑。

Socket、Thread类

在Box类中添加

1
2
3
4
5
6
private Thread thread = new Thread() {

public void run() {
System.out.println("Serializable thread");
}
};

运行发现,直接编译报错!

事实证明,不能对Thread进行序列化,若希望程序能便宜通过,我们对Thread变量添加static或transient修饰符即可。

0x04 完全定制序列化过程Externalizable

如果一个类要完全负责自己的序列化,则实现Externalizable接口,而不是Serializable接口。

Externalizable接口定义包括两个方法writeExternal()与readExternal()。需要注意的是:声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。
实例:

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
import java.io.FileInputStream;   
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.Serializable;
import java.io.Externalizable;
import java.io.IOException;
import java.lang.ClassNotFoundException;

public class ExternalizableTest1 {
private static final String TMP_FILE = ".externalizabletest1.txt";

public static void main(String[] args) {

testWrite();
testRead();
}

private static void testWrite() {
try {

ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(TMP_FILE));
Box box = new Box("desk", 80, 48);
out.writeObject(box);
System.out.println("testWrite box: " + box);

out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}

private static void testRead() {
try {

ObjectInputStream in = new ObjectInputStream(
new FileInputStream(TMP_FILE));
Box box = (Box) in.readObject();
System.out.println("testRead box: " + box);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

class Box implements Externalizable {
private int width;
private int height;
private String name;

public Box() {
}

public Box(String name, int width, int height) {
this.name = name;
this.width = width;
this.height = height;
}

public void writeExternal(ObjectOutput out) throws IOException {
}

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}

public String toString() {
return "["+name+": ("+width+", "+height+") ]";
}
}

0x05 参考文献

http://www.cnblogs.com/skywang12345/p/io_06.html

本文标题:Java序列化与反序列化基础

文章作者:Pino-HD

发布时间:2018年05月30日 - 18:05

最后更新:2018年05月30日 - 22:05

原始链接:https://pino-hd.github.io/2018/05/30/Java序列化与反序列化基础/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!