dcddc

西米大人的博客

0%

浅析若干Java序列化工具

在Java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有:
采用java对象的序列化和反序列化
把对象包装成JSON字符串传输
Google工具protoBuf的开源

为了便于说明各个做法的区别,分别对这三种做法进行阐述。 对UserVo对象进行序列化,class UserVo如下:

1
2
3
4
5
6
7
8
9
10
package serialize;
import java.util.List;

public class UserVo
{
private String name;
private int age;
private List<UserVo> friends;
//此处省略Getter和Setter方法
}

初始化一个UserVo实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UserVo user = new UserVo();
user.setName("zzh");
user.setAge(18);

UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);

List<UserVo> friends = new ArrayList<UserVo>();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);

采用java对象的序列化和反序列化

1
2
3
4
5
6
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(user);
oos.flush();
oos.close();
System.out.println(os.toByteArray().length);

序列化大小:205
优点:java原生支持,不需要提供第三方的类库,使用比较简单。
缺点:无法跨语言,字节数占用比较大,某些情况下对于对象属性的变化比较敏感。

把对象包装成JSON字符串传输

JSON工具类有许多种,这里列出三个比较流行的json工具类:Jackson,Gson,FastJson.

开源的Jackson

Jackson社区相对比较活跃,更新速度也比较快。Jackson对于复杂类型的json转换bean会出现问题,一些集合Map,List的转换出现问题。Jackson对于复杂类型的bean转换Json,转换的json格式不是标准的Json格式。

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
package serialize.json;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import serialize.UserVo;

public class JacksonTest
{
private UserVo user = null;
private JsonGenerator jsonGenerator = null;
private ObjectMapper objectMapper = null;

@Before
public void init()
{
user = new UserVo();
user.setName("zzh");
user.setAge(18);

UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);

List<UserVo> friends = new ArrayList<UserVo>();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);

objectMapper = new ObjectMapper();
try
{
jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(System.out,JsonEncoding.UTF8);
}
catch (IOException e)
{
e.printStackTrace();
}
}

@After
public void destory()
{
try
{
if(jsonGenerator != null)
{
jsonGenerator.flush();
}
if(!jsonGenerator.isClosed())
{
jsonGenerator.close();
}
jsonGenerator = null;
objectMapper = null;
user = null;
}
catch (IOException e)
{
e.printStackTrace();
}
}

@Test
public void writeJson()
{
try
{
jsonGenerator.writeObject(user);
System.out.println();
System.out.println(objectMapper.writeValueAsBytes(user).length);
// System.out.println(objectMapper.writeValueAsString(user).length());
}
catch (IOException e)
{
e.printStackTrace();
}
}

@Test
public void readJson()
{
String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17,\"friends\":null},{\"name\":\"qq\",\"age\":19,\"friends\":null}]}";
UserVo uservo = null;
try
{
uservo = objectMapper.readValue(serString, UserVo.class);
}
catch (IOException e)
{
e.printStackTrace();
}
System.out.println(uservo.getName());
}
}

序列化大小:111

Google的Gson

Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布第一版后已被许多公司或用户应用。
Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不需要例外额外的jar,能够直接跑在JDK上。而在使用这种对象转换之前需先创建好对象的类型以及其成员才能成功的将JSON字符串成功转换成相对应的对象。类里面只要有get和set方法,Gson完全可以将复杂类型的json到bean或bean到json的转换,是JSON解析的神器。Gson在功能上面无可挑剔,但是性能上面比FastJson有所差距。

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
package serialize.json;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;

import serialize.UserVo;

public class GsonTest
{
private UserVo user = null;

@Before
public void init()
{
user = new UserVo();
user.setName("zzh");
user.setAge(18);

UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);

List<UserVo> friends = new ArrayList<UserVo>();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);
}

@Test
public void writeJson()
{
try
{
String str = Gson.class.newInstance().toJson(user);//一行就可以搞定!!!
System.out.println(str);
System.out.println(str.length());
}
catch (InstantiationException | IllegalAccessException e)
{
e.printStackTrace();
}
}

@Test public void readJson()
{
String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
try
{
UserVo userVo = Gson.class.newInstance().fromJson(serString, UserVo.class);
System.out.println(userVo.getName());
}
catch (JsonSyntaxException | InstantiationException | IllegalAccessException e)
{
e.printStackTrace();
}
}
}

序列化大小:81

Gson和Jackson的区别是:如果你的应用经常会处理大的JSON文件,那么Jackson应该是你的菜。GSON在大文件上表现得相当吃力。如果你主要是处理小文件请求,比如某个微服务或者分布式架构的初始化,那么GSON当是首选。Jackson在小文件上的表现则不如人意。

阿里巴巴的FastJson

Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上
FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。

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
package serialize.json;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;

import com.alibaba.fastjson.JSON;

import serialize.UserVo;

public class FastJsonTest
{
private UserVo user = null;

@Before
public void init()
{
user = new UserVo();
user.setName("zzh");
user.setAge(18);

UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);

List<UserVo> friends = new ArrayList<UserVo>();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);
}

@Test public void writeJson()
{
String str = JSON.toJSONString(user);
System.out.println(str);
System.out.println(str.length());
}

@Test public void readJson()
{
String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
UserVo userVo = JSON.parseObject(serString,UserVo.class);
System.out.println(userVo.getName());
}
}

如果只是功能要求,没有性能要求,可以使用google的Gson,如果有性能上面的要求可以使用Gson将bean转换json确保数据的正确,使用FastJson将Json转换Bean。

Google工具protoBuf

protocol buffers 是google内部得一种传输协议,目前项目已经开源。它定义了一种紧凑得可扩展得二进制协议格式,适合网络传输,并且针对多个语言有不同得版本可供选择
protoBuf优点:1.性能好,效率高;2.代码生成机制,数据解析类自动生成;3.支持向前兼容和向后兼容;4.支持多种编程语言;5.字节数很小,适合网络传输节省io。缺点:1.应用不够广;2.二进制格式导致可读性差;3.缺乏自描述;
protoBuf是需要编译工具的,这里用的是window的系统。需要下载proto.exe和protobuf-java-2.4.1.jar;
1、将proto.exe放在当前工程目录下,然后编辑.proto文件,命名为UserVo.proto,如下所示

1
2
3
4
5
6
7
8
9
10
11
package serialize.protobuf;

option java_package = "serialize.protobuf";
option java_outer_classname="UserVoProtos";

message UserVo
{
optional string name = 1;
optional int32 age = 2;
repeated serialize.protobuf.UserVo friends = 3;
}

2、在命令行中利用protoc工具生成builder类
3、看到生成了UserVoProtos.java,由于这个java文件有1千行左右,篇幅限制不便罗列。
4、序列化和反序列化测试代码:

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 serialize.protobuf;

import org.junit.Before;
import org.junit.Test;

import com.google.protobuf.InvalidProtocolBufferException;

public class ProtoBufTest
{
UserVoProtos.UserVo.Builder user = null;

@Before public void init()
{
user = UserVoProtos.UserVo.newBuilder();
user.setName("zzh");
user.setAge(18);

UserVoProtos.UserVo.Builder f1 = UserVoProtos.UserVo.newBuilder();
f1.setName("jj");
f1.setAge(17);

UserVoProtos.UserVo.Builder f2 = UserVoProtos.UserVo.newBuilder();
f2.setName("qq");
f2.setAge(19);

user.addFriends(f1);
user.addFriends(f2);
}

@Test public void doSeri()
{
UserVoProtos.UserVo vo = user.build();
byte[] v = vo.toByteArray();
for(byte b:v)
{
System.out.printf("%02X ",b);
}
System.out.println();
System.out.println(v.length);
}

@Test public void doDeSeri()
{
byte[] v = new byte[]{0x0A, 0x03, 0x7A, 0x7A, 0x68, 0x10, 0x12, 0x1A, 0x06, 0x0A, 0x02, 0x6A, 0x6A, 0x10, 0x11, 0x1A, 0x06, 0x0A, 0x02, 0x71, 0x71, 0x10, 0x13};
try
{
UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(v);
System.out.println(uvo.getName());
}
catch (InvalidProtocolBufferException e)
{
e.printStackTrace();
}
}
}

序列化大小:23
工作机制:proto文件是对数据的一个描述,包括字段名称,类型,字节中的位置。
protoc工具读取proto文件生成对应builder代码的类库。protoc xxxxx –java_out=xxxxxx 生成java类库。
builder类根据自己的算法把数据序列化成字节流,或者把字节流根据反射的原理反序列化成对象