实际项目中,我们经常需要使用序列化工具来存储和传输对象。目前用得比较多的序列化工具有:jackson、fastjson、kryo、protostuff、fst 等,本文将简单对比这几款工具序列化和反序列化的性能。
本文使用 jmh 作为测试工具。
os:win 10
jdk:1.8.0_231
jmh:1.25
选择的序列化工具及对应的版本如下:
fastjson:1.2.74
jackson:2.11.3
kryo:5.0.0
fst:2.57
protostuff:1.7.2
为了公平,我尽量让测试用例中对序列化工具的用法更贴近实际项目,例如,kryo 的Kryo
对象不是线程安全的,实际项目中我们并不会每次使用就直接 new 一个新对象,而是使用 ThreadLocal 或者池来减少创建对象的开销。
本文使用的 java bean 如下。一个用户对象,一对一关联部门对象和岗位对象,其中部门对象又存在自关联。
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 普通属性--129个
private String id;
private String account;
private String password;
private Integer status;
// ······
/**
* 所属部门
*/
private Department department;
/**
* 岗位
*/
private Position position;
// 以下省略setter/getter方法
}
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
// 普通属性--7个
private String id;
private String parentId;
// ······
/**
* 子部门
*/
private List<Department> children;
// 以下省略setter/getter方法
}
public class Position implements Serializable {
private static final long serialVersionUID = 1L;
// 普通属性--6个
private String id;
private String name;
// ······
// 以下省略setter/getter方法
}
下面展示部分测试代码,完整代码见末尾链接。
JDK 提供了ObjectOutputStream
用于支持序列化,ObjectInputStream
用于反序列化。注意,使用 JDK 自带的序列化工具时,java bean 必须实现Serializable
,否则会抛出NotSerializableException
异常 。使用关键字 transient 修饰的成员属性不会被序列化。
// 序列化
@Benchmark
public byte[] jdkSerialize(CommonState commonState) throws Exception {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArray);
outputStream.writeObject(commonState.user);
outputStream.flush();
outputStream.close();
return byteArray.toByteArray();
}
// 反序列化
@Benchmark
public User jdkDeSerialize(JdkState jdkState) throws Exception {
ByteArrayInputStream byteArray = new ByteArrayInputStream(jdkState.bytes);
ObjectInputStream inputStream = new ObjectInputStream(byteArray);
User user = (User)inputStream.readObject();
inputStream.close();
assert "zzs0".equals(user.getName());
return user;
}
fastjson 由阿里团队开发,是目前最快的Java 实现的 json 库。 fastjson 的 API 非常简洁,并且支持一定程度的定制(例如,注解@JSONField
、枚举Feature
等定制序列化)。被人诟病的,可能是 fastjson 的 bug 比较多。
// 序列化
@Benchmark
public byte[] fastJsonSerialize(CommonState commonState) {
return JSON.toJSONBytes(commonState.user);
}
// 反序列化
@Benchmark
public User fastJsonDeSerialize(FastJsonState fastJsonState) {
User user = JSON.parseObject(fastJsonState.bytes, User.class);
assert "zzs0".equals(user.getName());
return user;
}
jackson 由 fasterxml 组织开发,相比 fastjson,有着更强大的功能、更高的稳定性、更好的扩展性、更丰富的定制支持。Spring 默认使用的 json 解析工具就是 jackson。
使用 jackson 需要注意,ObjectMapper
对象是线程安全的,可以重复使用。
// 序列化
@Benchmark
public byte[] jacksonSerialize(CommonState commonState, JacksonState jacksonState) throws Exception {
return jacksonState.objectMapper.writeValueAsBytes(commonState.user);
}
// 反序列化
@Benchmark
public User jacksonDeSerialize(JacksonState jacksonState) throws Exception {
User user = jacksonState.objectMapper.readValue(jacksonState.bytes, User.class);
assert "zzs0".equals(user.getName());
return user;
}
kryo 由 EsotericSoftware 组织开发,不兼容 jdk 自带序列化工具的数据,kryo 序列化后的数据要更小,至于 API 的简洁性方面,我觉得还是差了一些,一不小心就会采坑。使用 kryo 需要注意以下几点:
Kryo
对象不是线程安全的,可以使用ThreadLocal
或池来获取(本文使用池获取);kryo.setRegistrationRequired(false);
,因为同样的 Class 在不同的机器上注册编号很难保证一致;kryo.setReferences(true);
来避免。如果项目中不可能出现循环引用,则可以设置为 false 以提高性能。 // 序列化
@Benchmark
public byte[] kryoSerialize(CommonState commonState, KryoState kryoState) {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
Output output = new Output(byteArray);
Kryo kryo = kryoState.kryoPool.obtain();
kryo.writeClassAndObject(output, commonState.user);
kryoState.kryoPool.free(kryo);
output.flush();
output.close();
return byteArray.toByteArray();
}
//反序列化
@Benchmark
public User kryoDeSerialize(KryoState kryoState) throws Exception {
ByteArrayInputStream byteArray = new ByteArrayInputStream(kryoState.bytes);
Input input = new Input(byteArray);
Kryo kryo = kryoState.kryoPool.obtain();
User user = (User)kryo.readClassAndObject(input);
kryoState.kryoPool.free(kryo);
input.close();
assert "zzs0".equals(user.getName());
return user;
}
fst(fast-serialization)是由 RuedigerMoeller 开发,API 非常简洁。使用时需要注意,FSTConfiguration
对象可以重复使用。
其实,fst 也支持以 json 形式序列化,但是这一块的性能稍差而且用的人较少,这里就不提及了。
// 序列化
@Benchmark
public byte[] fstSerialize(CommonState commonState, FSTConfigurationState fSTConfigurationState) {
return fSTConfigurationState.fSTConfiguration.asByteArray(commonState.user);
}
// 反序列化
@Benchmark
public User fstDeSerialize(FSTState fstState) throws Exception {
User user = (User)fstState.fSTConfiguration.asObject(fstState.bytes);
assert "zzs0".equals(user.getName());
return user;
}
protostuff 是基于 google protobuf 开发而来(与 protobuf 相比,protostuff 在几乎不损耗性能的情况下做到了不用写.proto文件来实现序列化),不兼容 jdk 自带序列化工具的数据,序列化后的数据要更小。使用 protostuff 需要注意几点:
// 序列化
@Benchmark
public byte[] protostuffSerialize(CommonState commonStateme) {
Schema<User> schema = (Schema<User>)RuntimeSchema.getSchema(User.class);
return ProtostuffIOUtil.toByteArray(commonStateme.user, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
}
// 反序列化
@Benchmark
public User protostuffDeSerialize(ProtostuffState protostuffState) throws Exception {
User user = new User();
Schema<User> schema = (Schema<User>)RuntimeSchema.getSchema(User.class);
ProtostuffIOUtil.mergeFrom(protostuffState.bytes, user, schema);
assert "zzs0".equals(user.getName());
return user;
}
以下以吞吐量作为指标,相同条件下,吞吐量越大越好。
cmd 指令如下:
mvn clean package
java -ea -jar target/benchmarks.jar cn.zzs.serialize.SerializeTest -f 1 -t 1 -wi 10 -i 10
测试结果:
# JMH version: 1.25
# VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
# VM invoker: D:\growUp\installation\jdk1.8.0_231\jre\bin\java.exe
# VM options: -ea
# Warmup: 10 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
Benchmark Mode Cnt Score Error Units
SerializeTest.fastJsonSerialize thrpt 10 72.914 ± 1.651 ops/ms
SerializeTest.fstSerialize thrpt 10 252.735 ± 3.148 ops/ms
SerializeTest.jacksonSerialize thrpt 10 90.981 ± 3.394 ops/ms
SerializeTest.jdkSerialize thrpt 10 45.049 ± 0.623 ops/ms
SerializeTest.kryoSerialize thrpt 10 301.603 ± 3.147 ops/ms
SerializeTest.protostuffSerialize thrpt 10 260.144 ± 2.264 ops/ms
可以看到,序列化速度方面:kryo > protostuff > fst > jackson > fastjson > jdk。由于 bean 对象的不同,测试结果可能会有差异。
cmd 指令如下:
mvn clean package
java -ea -jar target/benchmarks.jar cn.zzs.serialize.DeSerializeTest -f 1 -t 1 -wi 10 -i 10
测试结果:
# JMH version: 1.25
# VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
# VM invoker: D:\growUp\installation\jdk1.8.0_231\jre\bin\java.exe
# VM options: -ea
# Warmup: 10 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
fastjson serialized data size:3044
fst serialized data size:1507
jackson serialized data size:3060
jdk serialized data size:4200
kryo serialized data size:1297
protostuff serialized data size:1543
Benchmark Mode Cnt Score Error Units
DeSerializeTest.fastJsonDeSerialize thrpt 10 54.956 ± 2.191 ops/ms
DeSerializeTest.fstDeSerialize thrpt 10 192.875 ± 3.541 ops/ms
DeSerializeTest.jacksonDeSerialize thrpt 10 63.998 ± 1.014 ops/ms
DeSerializeTest.jdkDeSerialize thrpt 10 13.870 ± 0.194 ops/ms
DeSerializeTest.kryoDeSerialize thrpt 10 230.786 ± 4.018 ops/ms
DeSerializeTest.protostuffDeSerialize thrpt 10 147.933 ± 3.032 ops/ms
可以看到,反序列化速度方面:kryo > fst > protostuff > jackson > fastjson > jdk,该结果和序列化基本一致,由于 bean 对象的不同,测试结果可能会有差异。
序列化数据的大小方面:kryo < fst < protostuff < fastjson < jackson < jdk。
以上数据仅供参考。感谢阅读。
相关源码请移步: serialize-tool-demo
本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/13948414.html