牛逼之前一定会有苦逼的岁月,但只有你像傻逼一样坚持了,才能拥有装逼的资本。
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/jackson-learning
本来一个小小的Jackson技术作为专栏写到二十一篇本已经够多,可以结束了。但据我收到的反馈,不少小伙伴希望对比当下中国程序员 最喜欢的两个JSON库:Jackson和Fastjson。为此,我决定用此篇作为本专栏的完结篇,善始善终,更为圆满。
说明:流行的JSON库其实还有谷歌的Gson,但据我了解中国程序员使用它的相对较少,所以本文撇开它不谈
毋庸置疑,Jackson是全球范围内最为流行的JSON库,但Fastjson
背靠大树阿里,在中国积累了不少“忠粉”,流行程度也不容小觑,甚至大有中国最流行的JSON库之趋势。
对于Jackson与Fastjson
是很多同学茶余饭后常讨论的小话题,毕竟正面相争必有些江湖恩怨。本文不会带有个人意见去对比这两个大作,因为感觉自己还不够格。所以我仅作为知识的搬运工,搜集一些资料展示给大家,省得同学们跑动跑西弄得晕头转向。
Java的JSON库比较多,本文只关心Jackson和Fastjson。
Jackson使用的版本号是:2.10.1
,Fastjson使用的版本号是1.2.62
,均为此时最新版本。
关于Jackson介绍,本处不再鳌诉,详请参见 本专栏 前2篇文章。
Fastjson
是一个Java语言编写的高性能的JSON处理器,由阿里巴巴开源。能直接跑在JDK上,无任何其它依赖。FastJson采用独创的算法,将 parse的速度提升到极致,超过所有 json库(官方自己说的)。
说明:
FastJson
由阿里的 温少 几乎凭一己之力开发出来的大作。同时他还几乎用一己之力开发出了有“最好的数据库连接池”之称的Druid
。它是阿里巴巴的初代开源人。
Fastjson有如下特点(摘抄自Fastjson的wiki主页):
fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。
fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。
fastjson有非常多的testcase,在1.2.11版本中,testcase超过3321个。每次发布都会进行回归测试,保证质量稳定。
fastjson的API十分简洁。
String text = JSON.toJSONString(obj); //序列化
VO vo = JSON.parseObject("{...}", VO.class); //反序列化
支持泛型,支持流处理超大文本,支持枚举,支持序列化和反序列化扩展。
介绍完了它俩,是骡子是马应该拉出来溜溜。所以下面将从多个维度对两者展开进行比较,这应该也是读者朋友们最为关心的部分。
关于性能的比较,演示代码均使用最常用的高层API,而非底层API,毕竟对比底层API的意义并不大,因为几乎不会使用。
关于性能比较这块,其实网上有非常多比较案例,各位也可参考。但是本文还是会自己书写几个case,这才能让读者信服,我自己也才能信服。
测试代码如下:
private static void jacksonSer(ObjectMapper mapper, Object obj) {
try {
mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
private static void fastjsonSer(Object obj) {
JSON.toJSONString(obj);
}
// 用于统计耗时的静态方法
// 这里仅是把Runnable当作@FunctionalInterface来使而已,并不是启线程
private static void run(String name, Runnable task) {
Instant start = Instant.now();
task.run();
Instant end = Instant.now();
System.out.printf("【%s】耗时: %s 毫秒\n", name, Duration.between(start, end).toMillis());
}
准备样本数据:
private static final int MAX = 1_0000;
private static final List<Integer> OBJS = new ArrayList<>();
static {
for (int i = 0; i < MAX; i++) {
OBJS.add(i);
}
}
test case代码:
@Test
public void fun1() {
System.out.println("MAX为:" + MAX);
run("jackson", () -> {
ObjectMapper mapper = new ObjectMapper();
jacksonSer(mapper, OBJS);
});
run("fastjson", () -> JSON.toJSONString(OBJS));
}
MAX的值从一万起步,逐渐增加,耗时情况如下:
MAX为:10000
【jackson】耗时: 335 毫秒
【fastjson】耗时: 79 毫秒
MAX为:100000
【jackson】耗时: 389 毫秒
【fastjson】耗时: 111 毫秒
MAX为:1000000
【jackson】耗时: 463 毫秒
【fastjson】耗时: 200 毫秒
MAX为:10000000
【jackson】耗时: 2720 毫秒
【fastjson】耗时: 2027 毫秒
MAX为:30000000
【jackson】耗时: 3691 毫秒
【fastjson】耗时: 7633 毫秒
咋一看,fastjson似乎比jackson快。你应该也能发现一个细节:Jackson对大数据量的敏感程度低于fastjson,然而量足够大时性能上反倒超过fastjson。
提示:若你自己尝试测试时发现OOM了,可提高你的
-Xms2048m -Xmx2048m
值再运行。
通过调整Xms值过程中,你会发现Fastjson对内存的占用是远高于Jackson的,因为它更容易OOM。
我看到网上好几篇文章通过这个现象,就下结论:Fastjson性能比Jackson高。其实这是非常不武断的,比如,我把测试代码稍作修改如下:
@Test
public void fun1() {
System.out.println("MAX为:" + MAX);
ObjectMapper mapper = new ObjectMapper(); // 单例
run("jackson", () -> jacksonSer(mapper, OBJS));
run("fastjson", () -> JSON.toJSONString(OBJS));
}
ObjectMapper
作为单例,并不需要每次都去new一次。再次reRun,结果如下:
MAX为:10000
【jackson】耗时: 70 毫秒
【fastjson】耗时: 84 毫秒
MAX为:100000
【jackson】耗时: 96 毫秒
【fastjson】耗时: 135 毫秒
MAX为:1000000
【jackson】耗时: 143 毫秒
【fastjson】耗时: 223 毫秒
MAX为:10000000
【jackson】耗时: 788 毫秒
【fastjson】耗时: 1657 毫秒
MAX为:30000000
【jackson】耗时: 5155 毫秒
【fastjson】耗时: 10442 毫秒
所以说:网上的同仁们,请你们测试Jackson性能的时候,把ObjectMapper当作单例来用,毕竟实际生产使用中我们也是这么干的。new一个新的ObjectMapper实例开销是较大的,测试需要保证公平性,不用故意黑Jackson嘛。
可能有的小伙伴还会怀疑说:Fastjson的run在后面,可能会受到前面run方法产生的垃圾,从而受到垃圾回收的影响。那么我现在对调run的先后顺序,reRun测试结果如下:
MAX为:10000
【fastjson】耗时: 98 毫秒
【jackson】耗时: 59 毫秒
MAX为:100000
【fastjson】耗时: 129 毫秒
【jackson】耗时: 87 毫秒
MAX为:1000000
【fastjson】耗时: 191 毫秒
【jackson】耗时: 132 毫秒
MAX为:10000000
【fastjson】耗时: 1465 毫秒
【jackson】耗时: 895 毫秒
MAX为:30000000
【fastjson】耗时: 4678 毫秒
【jackson】耗时: 1262 毫秒
或许这个结果让你震惊,你心目中的性能之王Jackson尽然“落败”。
当然或许你又会说 不应该用纯数字而应该用复杂些Java Bean
做试验,作为测试来说这种要求是完全合理的,比较case越全数据才越权威有效。
但是,我个人就不做太多的case覆盖了,毕竟这种case做起来比较简单,网上也有不少案例可参考,有兴趣的小伙伴可自行书写case。综合评价和我自己的整理,在性能对比上我给出这个结论:Jackson性能不输Fastjson。
说明:因为Fastjson内部使用了很多tricky写死的小技巧,并且还有很多“黑科技”:使用ThreadLocal来缓存buf、使用asm避免反射等等。
所以有理由相信它在处理稍复杂类型时速度会更快点,所以这里用了不输于还是挺中肯的,希望你也能同意。
下面引用Fasjson社区的一个维护者的话:
Fastjson兼容性的问题:由于fastjson也发展了很多年,阿里内外部用户很多,特别是早期为了自己的业务场景和容错,做了不少非标准化的tricky做法进去,这些东西都甩不掉了,导致了很多稍微复杂的场景下,fastjson的行为,跟其他主流库jackson、gson并不一致,这也是一个阻碍了它更大规模使用的原因。
文字说明来得永远没有代码这么直观,我就举个例子来进行说明:
private static void jacksonSer(ObjectMapper mapper, Object obj) {
try {
System.out.println("jackson:" + mapper.writeValueAsString(obj));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
private static void fastjsonSer(Object obj) {
System.out.println("fastjson:" + JSON.toJSONString(obj));
}
数据构造:
@Getter
@ToString
public class Person {
private String name = "YourBatman";
private Integer age = 18;
}
test case代码如下:
@Test
public void fun2() {
Person person = new Person();
fastjsonSer(person);
jacksonSer(new ObjectMapper(), person);
}
运行输出为:
fastjson:{"age":18,"name":"YourBatman"}
jackson:{"name":"YourBatman","age":18}
看似效果一样,但其实内部蕴含有如下不同:jackson输出默认维持字段的定义顺序,而fastjson默认帮你排序了。
说明:很显然保持字段定义顺序的
jackson
是更加合理的输出方式,也是规范标准做法
这个差异一般影响甚微。那么我对数据源稍加改造:
@ToString
public class Person {
private String name = "YourBatman";
private Integer age = 18;
}
reRun测试程序,输出:
fastjson:{}
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.yourbatman.jacksonandfastjson.beans.Person and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
...
对于空Bean:fastjson输出"{}",Jackson默认是抛出异常,当然你可以关掉此特征值FAIL_ON_EMPTY_BEANS
来达到同样效果。这个差异也影响较小,毕竟在Spring MVC
环境下这个特征值会把它关掉。
对数据源再次改造如下:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NON_PRIVATE)
@ToString
public class Person {
protected String name = "YourBatman";
Integer age = 18;
}
reRun测试程序,输出:
fastjson:{}
jackson:{"name":"YourBatman","age":18}
这个差异就非常致命了。Fastjson
只能识别访问权限为public的字段/get方法为一个属性,而Jackson提供了标准注解@JsonAutoDetect
实现精准控制。
这个差异在你做DDD领域驱动编程的时候,你需要通过属性的访问权限来达到控制的效果,并且领域还需要序列化/反序列化,这用Fastjson几乎是不能完成的,因为这一点成为了我本人放弃Fastjson的最直接理由。
当然,Fastjson在处理BigDecimal
,以及1/0 -> true/false转换时的表现和Jackson/Gson
均不太一样,这就是为什么说Fastjson一切为了速度而对兼容性、标准型做出了妥协的原因。
so,在兼容性(标准)方面的结论是:Jackson明显优于Fastjson
。
Jackson的扩展性实在太好了。各种奇葩需求都能得到满足:
通过Module模块的扩展,还能支持XML、Properties、Yaml
…
相比较而言,Fastjson在扩展性这方面就显得弱很多。毕竟它代码中有很多写死的实现,天生决定了扩展性会受到限制。当然这和它最初定位是使用在阿里系内使然,所以有很多写死的(比如判断阿里虚拟机名称)代码在里面,所以可扩展性方面并不是它关注的首要重点。
作为流行的、面向应用层的库,安全是非常重要的。它俩都出现过多次安全漏洞,最近比较“出名”的是:
Fastjson:
Jackson:
关于Jackson的漏洞也不算少,都收录在这里https://www.anquanke.com/vul,有兴趣者可进去看看
安全方面总体来说:不太好评价,平分秋色吧。
这个标题是网络上比较流行的一个“热搜”,因此这里我不废话,“答案”你完全可以参考知乎的这篇文章:https://www.zhihu.com/question/44199956。里面有几十个回答很多讲解得都很到位,各位看官亦可从此丰富自己的知识。
说明:此知乎链接里的所有回答,建议一定要看,一定要看,一定要看
JSON
不管是在Web开发,还是服务器开发中是最常见的数据传输格式。竟然是最常用,那么在JSON结构解析的性能上我们是否该关心呢?看下面一个例子:
比如一个REST调用全程100ms,它的分配大概是这样的:
- 网络传输几十毫秒
- 访问DB几十毫秒
- 序列化/反序列化,几毫秒
不同的JSON库,性能差异在毫秒之间,所以如果你真的特别执拗要提升这个性能,那你的做法会有如下特点:投资较大(毕竟换了一个库),收益极小,风险高。我相信这种事一般领导都是不会支持你去这么干的。
因此,Fastjson
妥协规范兼容性而选择性能速度,对绝大多数用户来说是不值当的 (可能对阿里内部是划算的,毕竟它的量非常大,压榨性能会有成效)。再加上Jackson历史悠久,可扩展性极强,因此实在没有什么动力去换一个JSON库。
呼应本专栏第一篇提出的观点:如果你的工程只允许有一个JSON库的话,不用多想,毫无疑问,请选择Jackson吧
关于Jackson和Fastjson的恩怨情仇到这里就讲完了,不可否认它俩都是非常优秀的JSON库,Fastjson固然优秀,但综合对比会发现Jackson更胜一筹。
虽然国产软件还有很长的路要走,但毕竟Fastjson
几乎是以一己之力开除出来的优秀产品,所谓我还是得在此呼吁一句:多多使用国产,多多支持国产,不断的更新迭代,相信会有长足的进步的。
到这里,整个[享学Jackson]
专栏就全部结束了,感谢大家的一路陪伴和支持,希望此专栏对你的职业生涯有帮助,给你的编码路途添砖加瓦。
当然,这不是关于JSON的所有内容,还有很多应用场景方面的比如:Redis如何使用Jackson?Feign中又是如何使用Jackson完成序列化的等等。一个专栏不可能做到全覆盖,所以相关场景的讲解,我会在后续文章中以免费的形式发布出来,敬请持续关注。
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。