[享学Jackson] 二十二、Jackson与Fastjson的恩怨情仇(完结篇)

本文对比了Jackson和Fastjson两个流行的JSON库,分析了它们在性能、兼容性、可扩展性和安全方面的差异。尽管Fastjson速度较快,但Jackson在兼容性和可扩展性上更胜一筹,且在实际应用中,性能差异并不足以成为更换库的主要原因。综合来看,Jackson是更好的选择。
摘要由CSDN通过智能技术生成

牛逼之前一定会有苦逼的岁月,但只有你像傻逼一样坚持了,才能拥有装逼的资本。

–> 返回专栏总目录 <–
代码下载地址: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

关于Jackson介绍,本处不再鳌诉,详请参见 本专栏 前2篇文章

Fastjson

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的扩展性实在太好了。各种奇葩需求都能得到满足

  • null值的处理
  • 空字符串的处理
  • bool值和0/1的处理
  • 字段缺失处理
  • 默认值处理
  • 访问权限处理

通过Module模块的扩展,还能支持XML、Properties、Yaml
相比较而言,Fastjson在扩展性这方面就显得弱很多。毕竟它代码中有很多写死的实现,天生决定了扩展性会受到限制。当然这和它最初定位是使用在阿里系内使然,所以有很多写死的(比如判断阿里虚拟机名称)代码在里面,所以可扩展性方面并不是它关注的首要重点。


安全

作为流行的、面向应用层的库,安全是非常重要的。它俩都出现过多次安全漏洞,最近比较“出名”的是:

Fastjson

  • 2019年7月:“0day”问题 --> 反序列化漏洞补丁绕过
  • 2019年9月:字符串为\x为开头时进入死递归安全漏洞。严重可导致服务瘫痪

Jackson
关于Jackson的漏洞也不算少,都收录在这里https://www.anquanke.com/vul,有兴趣者可进去看看

安全方面总体来说:不太好评价,平分秋色吧。


Fastjson这么快老外为啥还是热衷 Jackson?

这个标题是网络上比较流行的一个“热搜”,因此这里我不废话,“答案”你完全可以参考知乎的这篇文章: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完成序列化的等等。一个专栏不可能做到全覆盖,所以相关场景的讲解,我会在后续文章中以免费的形式发布出来,敬请持续关注。


关注A哥

AuthorA哥(YourBatman)
个人站点www.yourbatman.cn
E-mailyourbatman@qq.com
微 信fsx641385712
活跃平台
公众号BAT的乌托邦(ID:BAT-utopia)
知识星球BAT的乌托邦
每日文章推荐每日文章推荐

BAT的乌托邦

往期精选

jacksonfastjson都是用于处理JSON数据的Java库。它们提供了序列化和反序列化JSON数据的功能。 在序列化方面,这两个库有一些差异。fastjson默认情况下不会序列化null值,也就是说如果一个属性的值是null,在序列化后,该属性将不会出现在JSON字符串中。而jackson则默认会序列化null值,即使属性的值为null,该属性也会在JSON字符串中呈现。 两个库在序列化时也有一些其他的功能配置。例如,fastjson可以通过设置fastjson的序列化过滤器进行定制化处理,还可以美化输出JSON字符串等。而jackson可以全局配置序列化的行为,比如将驼峰命名转换为下划线命名,忽略某些属性的序列化等。 在反序列化方面,fastjsonjackson都提供了相应的功能。它们可以将JSON字符串转化为Java对象。 总结来说,jacksonfastjson都是功能强大的Java库,用于处理JSON数据。它们在序列化和反序列化方面有一些差异,并且提供了不同的配置选项来满足不同的需求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [jacksonfastjson](https://blog.csdn.net/xuxuxux123/article/details/126625323)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [JacksonFastJson快速入门(整合SpringMVC)](https://blog.csdn.net/qq_45173404/article/details/108417438)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值