Java序列化和反序列化的几种方式

前言

在Java中,我们知道可以随意创建对象,只要对象未被GC回收,我们都可以继续在程序里使用,但这些对象只是存在于JVM内存中的,我们JVM一旦停止,这些对象就消失不见了。

经常有些时候,我们需要把这些对象持久化下来,再次需要时,再重新把对象读取出来,Java中有一种机制,对象序列化机制(object serialization)便可以帮我们完成相关功能。

对象序列化,可以方便的把对象状态保存为字节数组,可以通过字节流进行远程网络传输等,接收到字节流,通过反序列化机制,可以将字节数组转换为相关对象。

常说的RPC远程调用,相关传输对象的生成类就必须实现序列化以便在网络间传输。

正文

Serializable接口

在Java中,我们最常用的实现序列化和反序列化的方法就是相关类实现 java.io.Serializable 接口了,这也是Java给我们提供的一个方便的API。

我们创建一个Apple类,实现序列化接口,通过测试,可以看到相关对象生成的字节码和反序列化后的对象。

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
public class Apple implements Serializable {
private String color;
private int weight;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Apple(String color, int weight) {
this.color = color;
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"color='" + color + '\'' +
", weight=" + weight +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test {
public static void main(String[] args) throws IOException,ClassNotFoundException{
byte [] bytes = null;
Apple apple = new Apple("red",150);
//序列化
try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)
){
oos.writeObject(apple);
bytes = baos.toByteArray();
for (byte b : baos.toByteArray()) {
System.out.print(Byte.toString(b) + " ");
}
}
System.out.println();
//反序列化
try (
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais)
) {
System.out.println(ois.readObject().toString());
}
}
}

测试输出结果:

upload successful

当我们去掉Apple类的Serializable接口后,执行测试会抛出异常,说明对象无法被序列化。

upload successful

序列化ID(serialVersionUID)

JVM虚拟机是否可以对某个对象进行反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的点是两个类的序列化ID是否一致(就是 private static final long serialVersionUID)。

序列化ID有两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化ID有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

序列化的实现方式

在Java中,我们还可以利用其它方式对对象进行序列化,我总结了几种序列化方式如下。

让我们一起来看一下:

我们提供一个序列化与反序列化通用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface Serializer {
/**
* 序列化名称
* @return
*/
String name();
/**
* 序列化
* @param obj
* @return
* @throws IOException
*/
byte[] serialize(Object obj) throws IOException ;
/**
* 反序列化
* @param bytes
* @return
* @throws IOException
*/
Object deserialize(byte[] bytes) throws IOException ;
}

标准的Java序列化

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
public class JavaSerializer implements Serializer {
@Override
public String name() {
return "java";
}
@Override
public byte[] serialize(Object obj) throws IOException {
try(
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
){
oos.writeObject(obj);
return baos.toByteArray();
}
}
@Override
public Object deserialize(byte[] bits) throws IOException{
if(bits == null || bits.length == 0) {
return null;
}
try (
ByteArrayInputStream bais = new ByteArrayInputStream(bits);
ObjectInputStream ois = new ObjectInputStream(bais);
){
return ois.readObject();
}catch (ClassNotFoundException e){
throw new RuntimeException(e);
}
}
}

可以看到序列化就是我们将对象通过ObjectOutputStream转化为ByteArrayOutputStream字节流,反序列化就是将字节流转换为对象流并读取。

FST实现序列化

需要引入相关jar包

1
2
3
4
5
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>2.57</version>
</dependency>
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
public class FSTSerializer implements Serializer {
@Override
public String name() {
return "fst";
}
@Override
public byte[] serialize(Object obj) throws IOException {
try (
ByteArrayOutputStream out = new ByteArrayOutputStream();
FSTObjectOutput fout = new FSTObjectOutput(out);
){
fout.writeObject(obj);
fout.flush();
return out.toByteArray();
}
}
@Override
public Object deserialize(byte[] bytes) throws IOException {
if(bytes == null || bytes.length == 0) {
return null;
}
try (
FSTObjectInput in = new FSTObjectInput(new ByteArrayInputStream(bytes));
){
return in.readObject();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

Kryo实现序列化

需要引入相关jar包

1
2
3
4
5
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.0.0-RC4</version>
</dependency>
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
public class KryoSerializer implements Serializer {
private final static Kryo kryo = new Kryo();
@Override
public String name() {
return "kryo";
}
@Override
public byte[] serialize(Object obj) throws IOException {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos)) {
kryo.register(obj.getClass());
kryo.writeClassAndObject(output, obj);
output.flush();
return baos.toByteArray();

}
}
@Override
public Object deserialize(byte[] bits) throws IOException {
if (bits == null || bits.length == 0) {
return null;
}
try (ByteArrayInputStream bais = new ByteArrayInputStream(bits);
Input ois = new Input(bais)
) {
return kryo.readClassAndObject(ois);
}
}
}

KryoPool实现序列化

由于kryo创建的代价相对较高,我们可以使用一个KryoPool池来管理Kryo,使用空间换取时间,提高运行效率。

我们使用一个双端队列来对Kryo进行管理,相关代码如下:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
public class KryoPoolSerializer implements Serializer{
/**
* Kryo 的包装
*/
private static class KryoHolder {
private Kryo kryo;
static final int BUFFER_SIZE = 1024;
/**
* reuse
*/
private Output output = new Output(BUFFER_SIZE, -1);
private Input input = new Input();
KryoHolder(Kryo kryo) {
this.kryo = kryo;
}
}

interface KryoPool {
/**
* get o kryo object
* @return KryoHolder instance
*/
KryoHolder get();

/**
* return object
* @param kryo holder
*/
void offer(KryoHolder kryo);
}

/**
* 由于kryo创建的代价相对较高 ,这里使用空间换时间
* 对KryoHolder对象进行重用
*/
public static class KryoPoolImpl implements KryoPool {
/**
* default is 1500
* online server limit 3K
*/
/**
* thread safe list
*/
private final Deque<KryoHolder> kryoHolderDeque=new ConcurrentLinkedDeque<KryoHolder>();

private KryoPoolImpl() {
}

/**
* @return KryoPool instance
*/
public static KryoPool getInstance() {
return Singleton.pool;
}

/**
* get o KryoHolder object
*
* @return KryoHolder instance
*/
@Override
public KryoHolder get() {
// Retrieves and removes the head of the queue represented by this table
KryoHolder kryoHolder = kryoHolderDeque.pollFirst();
return kryoHolder == null ? creatInstnce() : kryoHolder;
}

/**
* create a new kryo object to application use
* @return KryoHolder instance
*/
public KryoHolder creatInstnce() {
Kryo kryo = new Kryo();
kryo.setReferences(false);
return new KryoHolder(kryo);
}

/**
* return object
* Inserts the specified element at the tail of this queue.
*
* @param kryoHolder ...
*/
@Override
public void offer(KryoHolder kryoHolder) {
kryoHolderDeque.addLast(kryoHolder);
}

/**
* creat a Singleton
*/
private static class Singleton {
private static final KryoPool pool = new KryoPoolImpl();
}
}

@Override
public String name() {
return "Kryo_Pool";
}

/**
* Serialize object
* @param obj what to serialize
* @return return serialize data
*/
@Override
public byte[] serialize(Object obj) throws IOException {
KryoHolder kryoHolder = null;
if (obj == null){
throw new RuntimeException("obj can not be null");
}
try {
kryoHolder = KryoPoolImpl.getInstance().get();
kryoHolder.kryo.register(obj.getClass());
//reset Output -->每次调用的时候 重置
kryoHolder.output.reset();
kryoHolder.kryo.writeClassAndObject(kryoHolder.output, obj);
// 无法避免拷贝 ~~~
return kryoHolder.output.toBytes();
} catch (RuntimeException e) {
e.printStackTrace();
throw new RuntimeException("Serialize obj exception");
} finally {
KryoPoolImpl.getInstance().offer(kryoHolder);
//GC
obj = null;
}
}

/**
* Deserialize data
* @param bytes what to deserialize
* @return object
*/
@Override
public Object deserialize(byte[] bytes) throws IOException {
KryoHolder kryoHolder = null;
if (bytes == null){
throw new RuntimeException("bytes can not be null");
}
try {
kryoHolder = KryoPoolImpl.getInstance().get();
//call it ,and then use input object ,discard any array
kryoHolder.input.setBuffer(bytes, 0, bytes.length);
return kryoHolder.kryo.readClassAndObject(kryoHolder.input);
} catch (RuntimeException e) {
throw new RuntimeException("Deserialize bytes exception");
} finally {
KryoPoolImpl.getInstance().offer(kryoHolder);
// for gc
bytes = null;
}
}
}

Jackson 实现序列化和反序列化

Jackson也可以实现相关序列化和反序列化功能,需要引入jackson 的jar包。

使用writeValueAsBytes和readValue方法即可完成相关功能。

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.3</version>
</dependency>

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JacksonSerializer implements Serializer{

private static final ObjectMapper mapper = new ObjectMapper();
@Override
public String name() {
return "Jackson";
}

@Override
public byte[] serialize(Object obj) throws IOException {
return mapper.writeValueAsBytes(obj);
}

@Override
public Object deserialize(byte[] bytes) throws IOException {
return mapper.readValue(bytes,Object.class);
}
}

FastJson实现序列化和反序列化

FastJson实现序列化与反序列化,需要引入相关jar包,如下:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FastJsonSerializer implements Serializer{
@Override
public String name() {
return "FastJson";
}

@Override
public byte[] serialize(Object obj) throws IOException {
return JSON.toJSONString(obj, SerializerFeature.WriteClassName).getBytes();
}

@Override
public Object deserialize(byte[] bytes) throws IOException{
return JSON.parse(new String(bytes), Feature.SupportAutoType);
}
}

以上的序列化与反序列化的对象都需要实现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
public class SerializationUtils {
public static void main(String[] args) throws IOException {
Apple apple =new Apple();
apple.setColor("red");
apple.setWeight(100);

printData(new JavaSerializer(),apple);
printData(new FSTSerializer(),apple);
printData(new KryoSerializer(),apple);
printData(new KryoPoolSerializer(),apple);
printData(new JacksonSerializer(),apple);
printData(new FastJsonSerializer(),apple);

}
public static void printData(Serializer serializer,Apple apple) throws IOException{
long start = System.currentTimeMillis();
byte[] bits = serializer.serialize(apple);
System.out.println(serializer.name()+"序列化所需时间:"+(System.currentTimeMillis()-start)+"ms");
System.out.println(serializer.name()+"序列化后字节码长度:"+bits.length);
long start1 = System.currentTimeMillis();
Object obj = serializer.deserialize(bits);
System.out.println(serializer.name()+"反序列化所需时间:"+(System.currentTimeMillis()-start1)+"ms");
System.out.println(serializer.name()+"反序列化后对象:"+obj.toString());
}
}

可以看到输出后的结果:

upload successful

根据结果判断正确性后,也大致能看出各种序列化方式的一些优点和缺点。

  • JavaSerializer 明显的优点是不用引用包,也是Java程序默认的序列化方式,但是其序列化后占用空间是几种序列化方式里最大的,如果遇到大对象序列化,处理起来可能就比较力不从心了。

  • FSTSerializer、JacksonSerializer、FastJsonSerializer 它们是一种比较适中的序列化方式,序列化后的字节比Java方法少,时间也差不多。

  • KryoSerializer 是一种比较优异的序列化方式,可以看到它的序列化后的字节很短,占用空间少,且序列化和反序列化时间短。

  • KryoPoolSerializer 这种相当于KryoSerializer的改进版,利用了一部分内存空间,进一步降低了序列化和反序列化的时间。

正因为Kryo如此高效的序列化和反序列化性能,因此在大数据领域应用广泛。如Apache的spark、hive等。

如果需要更准确的结果比较各种序列化方式的性能,可以创建大量对象并对它们进行序列化记录时间等参数比较,这儿就不过多讨论了。

结语

通过对序列化和反序列化的简单介绍,并比较了一些常用的序列化方式,我们对对象的序列化与反序列化有了更进一步的认知。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道