一、先说结论
这次测试了三个 JSON 序列化的例子,综合来看,最快的都是 fastjson,遥遥领先第二名 Gson 30 倍!
性能测试的运行环境:
CPU:Intel i5-12490f
JDK:Azul Zulu JDK 17.0.7
JMH:1.37
Gson:2.10.1
fastjson:2.0.43
Jackson:2.16.0
为了方便大家复测,我选了一个结构清晰的例子贴在这里:
被测对象(Staff):
java
// import 省略
public class Staff {
private String name = "LIZI";
private String id = "007";
private Department department = new Department();
private BigDecimal salary = new BigDecimal("123.456");
public static class Department {
private Integer id = 2;
private Integer name = 3;
private Integer staffCount = 4;
}
// Getter、Setter 省略
}
JMH(Java Microbenchmark Harness)测试案例:
java
// import 省略
public class SerializeBench {
// 下面的 JSONRunnerHolder 用来初始化 ObjectMapper 和 Gson 对象
@Fork(value = 1, warmups = 1)
@Benchmark
@BenchmarkMode(Mode.Throughput)
public Object bench_jackson(JSONRunnerHolder runnerHolder) throws JsonProcessingException {
return runnerHolder.OM.writeValueAsString(new Staff());
}
@Fork(value = 1, warmups = 1)
@Benchmark
@BenchmarkMode(Mode.Throughput)
public Object bench_gson(JSONRunnerHolder runnerHolder) {
return runnerHolder.GSON.toJson(new Staff());
}
@Fork(value = 1, warmups = 1)
@Benchmark
@BenchmarkMode(Mode.Throughput)
public Object bench_fastjson() {
return JSON.toJSONString(new Staff());
}
}
经过五轮测试迭代,有了这样的数据:
先解释下吞吐量:
吞吐量(Throughput) = 完成的操作数量(Operations)/ 消耗的时间(Time)
吞吐量就是每秒能完成的序列化操作数,吞吐量越大,性能越好。
在 JMH 结果中,fastjson 平均每秒能完成 593 万次 序列化操作,是第二名 Gson 的 30 倍,是第三名 Jackson 的 300 倍!理性得看,性能不会有这么大的差距,但我们还是可以得出一个相对的结论:fastjson 是最快的 Java JSON 序列化器!
咋会这么快?!
二、(反)序列化与JSON
(反)序列化在前后端"沟通"及后端之间的协作中扮演着关键角色。在内存中,结构化的不连续数据需要转化为连续的字节流,才能传输到 I/O 设备(如网络、硬盘)。
提问:不转化为连续数据会咋样?
回答:如果直接把对象所在的一整块数据"打包"发送出去,这样的"块"是乱的!接收方将接收到一个混乱的"块",其中可能包含其他对象的数据,数据的内容也不紧凑。
通常根据生成数据的格式,(反)序列化分为两类方法:二进制 和文本。 二进制生成的数据基本没有可读性,但是生成的内容小,生成速度快,常用的技术有 ProtoBuf、Avro。
JSON(JavaScript Object Notation)则是一种"文本方法",可读性非常好,它最早由 JavaScript 用来展示对象格式。
json
// 这是一个例子,name 字段是文本,age 是数字,serializer 是数组,这些是不是一样就看出来啦
{
"name": "LiZi",
"age": 18,
"serializer": ["fastjson", "jackson", "gson"]
}
三、Jackson、Gson、fastjson 谁更好
其实很难说某一类技术更好,我们要基于实际需求和场景反复权衡。我根据下面四个维度对他们做了对比,以帮助大家按需选用。
Jackson | Gson | fastjson | |
---|---|---|---|
性能 | 24K osp/s | 205K ops/s | 5.9M ops/s |
生态 | >240K Depends | >74K Depends | >1.5K Depends |
安全性 | 87 CVE(漏洞) | 4 CVE | 3 CVE |
简便性 | 依赖包多,有模板操作 | 有模板操作 | 工具类,无模板 |
fastjson 性能好,漏洞少(只有 3 个 CVE),使用起来更方便:不用创建 ObjectMapper/Gson 这样的模板对象,但用户却是最少的。
根据 Maven Central 统计的依赖包下载量,fastjson 在 2023 年被依赖的数量只有约 1500 个,远低于第二名 Gson 的 74000 个,即使考虑到 Maven 私有镜像,这个数字也远远落后。
在2022年以前,fastjson 在国内 Java 圈的使用量较为可观。然而,自从 CVE-2022-25845 漏洞披露了 fastjson 自动类型转换存在问题后,fastjson 的声誉受到严重打击,许多企业将其列入"黑名单"。尽管 fastjson 已经修复了问题并推出了 fastjson2,其用户量仍未恢复到之前的水平。
在选择技术时,我们要权衡很多因素。虽然 fastjson 在性能、安全性和简便性方面表现出色,但在稳定性至关重要的生产环境,Jackson 庞大的用户基数使得它成为了一个不可忽视的选择。而且,(反)序列化对接口性能的最终影响又有多大呢?
四、fastjson 咋这么快?
想必大家一直在好奇 fastjson 为什么可以这么快,fastjson 的开发者温绍锦总结到:
-
"通过 ASM 动态 JIT 的方式针对每个 JavaBean 生成 Parser"。
ASM 是一种运行时生成字节码的技术。字节码是 JVM 的 "指令集"。fastjson 利用 ASM 在 JVM 运行时生成了代码,这些代码就像是我们给对象写了个 toString 方法来产生 JSON 字符串。序列化不再需要反射,也不用每次都扫描对象内容,速度自然快了。
typescript// 我自己写了个 JSON 序列化,不让 ASM 自动生成了 public String toString() { return "{" + ""name": "" + name + """ + ", "age":" + age + "}"; }
-
"很多编程技巧"
fastjson 使用了 JavaLangAccess 、Unsafe这样的底层类,着重优化字符串处理,使得字符串的构建、遍历和编码都有了很大的速度提升。具体技巧可以看看:fastjson_string_codec_methods · alibaba/fastjson2 Wiki · GitHub
在技术上,fastjson 是优秀的。"每提升 1%,就需要 100% 的付出",在性能上,fastjson 相比竞品却是指数级提升,需要的投入想必不少。fastjson 的开发者是专注且创新的,这种精神值得学习!
五、参考资料
- openjdk/jmh: openjdk.org/projects/co...
- CVE - Search CVE List
- central.sonatype.com/
- fastjson实现方式 · Issue #4102 · alibaba/fastjson
- java - ASM在FastJson中的应用 - ksfzhaohui技术专栏 - SegmentFault 思否