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

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

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/jackson-learning

目录

    • 前言
    • 正文
      • 简介
        • Jackson
        • Fastjson
          • 速度快
          • 使用广泛
          • 测试完备
          • 使用简单
          • 功能完备
      • 对比
        • 性能
        • 兼容性(规范、标准化)
        • 可扩展性
        • 安全
      • Fastjson这么快老外为啥还是热衷 Jackson?
      • 综合评价
    • 总结
      • 声明

前言

本来一个小小的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完成序列化的等等。一个专栏不可能做到全覆盖,所以相关场景的讲解,我会在后续文章中以免费的形式发布出来,敬请持续关注。

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

  • [享学Jackson] 一、初识Jackson – 世界上最好的JSON库
  • [享学Jackson] 二、jackson-core之流式API与JsonFactory、JsonGenerator、JsonParser
  • [享学Jackson] 三、jackson-databind之ObjectMapper与数据绑定、树模型
  • [享学Jackson] 四、控制Jackson行为的特征们之JsonFactory.Feature、JsonGenerator.Feature、JsonParser.Feature
  • [享学Jackson] 五、控制Jackson行为的特征们之JsonWriteFeature、JsonReadFeature
  • [享学Jackson] 六、控制Jackson行为的特征们之MapperFeature、SerializationFeature、DeserializationFeature
  • [享学Jackson] 七、Jackson使用bit位运算来开启/禁用Feature的原理解析
  • [享学Jackson] 八、jackson-databind数据绑定基础配置之BaseSettings、MapperConfig、MapperConfigBase
  • [享学Jackson] 九、jackson-databind数据绑定序列化/反序列化配置之SerializationConfig、DeserializationConfig
  • [享学Jackson] 十、jackson-databind序列化之ObjectMapper序列化原理、序列化器匹配原理
  • [享学Jackson] 十一、jackson-databind之JsonSerializer序列化器全解析
  • [享学Jackson] 十二、jackson-databind反序列化之ObjectMapper反序列化原理、JsonDeserializer反序列化器全解析
  • [享学Jackson] 十三、jackson-annotation注解模块全解析及Jackson注解大全
  • [享学Jackson] 十四、深入理解Jackson的Module模块化设计及原理分析
  • [享学Jackson] 十五、第三方模块Module的深度实践:JavaTimeModule、JSR310Module、ParameterNamesModule、Jdk8Module
  • [享学Jackson] 十六、Jackson在Spring MVC中的使用之Date、JSR310时间类型的处理
  • [享学Jackson] 十七、spring-web整合Jackson源码解析之Jackson2ObjectMapperBuilder
  • [享学Jackson] 十八、Spring容器深度整合Jackson的桥梁之SpringHandlerInstantiator
  • [享学Jackson] 十九、Spring下使用ObjectMapper的正确姿势 — Jackson2ObjectMapperFactoryBean
  • [享学Jackson] 二十、Spring MVC下的Jackson — MappingJackson2HttpMessageConverter
  • [享学Jackson] 二十一、Spring Boot下的Jackson — JacksonAutoConfiguration自动配置
  • [享学Jackson] 二十二、Jackson与Fastjson的恩怨情仇(完结篇)

你可能感兴趣的:(享学Jackson)