简单高性能的Json解析器: Jason

1,400 阅读4分钟

最近尝试用Java写了一个Json解析器,支持绑定到类上创建对象.

暂时不想支持很多特性,只想如何最高效地把Json反序列化成对象.

目前成果还比较初步,但初衷已经满足了. 开源出来请大家提出任何意见以继续改进.

性能测试程序及版本(均为目前最新发布版本):

 Jackson: 2.10.2
FastJson: 1.2.62
Dsl-Json: 1.9.5
Jsoniter: 0.9.23 (含javassist: 3.26.0)
   Jason: 当前版本(2020-02-25 22:58:35)

性能测试结果(时间越短越好) (测试环境: AdoptOpenJDK 13.0.2, 默认的JVM参数):

Jackson: 5884ms (使用Afterburner插件实时生成字节码)
FastJson: 4299ms (实时生成字节码)
Dsl-Json: 3799ms (非生成代码版本,生成代码版本用起来很繁琐,有人测过请在评论中留言)
Jsoniter: 2182ms (实时生成代码)
   Jason: 1841ms (无生成代码,使用高性能反射)

测试一轮(反序列化1000万个对象)的内存分配量:

 Jackson: 8016MB
FastJson: 4428MB
Dsl-Json: 1530MB
Jsoniter: 1374MB (除了反序列化的对象本身之外几乎无开销)
   Jason: 1374MB (除了反序列化的对象本身之外几乎无开销)

性能测试代码:

import com.alibaba.fastjson.JSON;
import com.dslplatform.json.DslJson;
import com.dslplatform.json.runtime.Settings;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import com.jsoniter.JsonIterator;
import com.jsoniter.spi.DecodingMode;
import jason.Jason;

public class BenchJson {
    public static class C { // 测试的类
        public int a;
        public String b;
        public C c;
    }

    // 测试的JSON字符串
    static String str = "{\"b\":\"xyz\", \"a\":123,\"c\":{\"a\":456,\"b\":\"abc\"}}";
    static byte[] bytes = str.getBytes();
    static final int TEST_COUNT = 10_000_000;

    static void testJackson() throws Exception { // jackson-*-2.10.2.jar四个文件总大小1.93MB
        long t = System.nanoTime(), v = 0;
        ObjectMapper om = new ObjectMapper();
        om.registerModule(new AfterburnerModule()); // 实测这里Afterburner插件效果不理想,只提速15%
        for (int i = 0; i < TEST_COUNT; ++i) {
            C c = om.readValue(bytes, C.class);
            v += c.a + c.c.a;
        }
        System.out.println(" Jackson: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
    }

    static void testFastJson() { // fastjson-1.2.62.jar大小643KB
        long t = System.nanoTime(), v = 0;
        for (int i = 0; i < TEST_COUNT; ++i) {
            C c = JSON.parseObject(str, C.class); // 只有FastJson使用String输入比byte[]性能更高
            v += c.a + c.c.a;
        }
        System.out.println("FastJson: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
    }

    static void testDslJson() throws Exception { // dsl-json-1.9.5.jar和dsl-json-java8-1.9.5.jar总大小468KB
        long t = System.nanoTime(), v = 0;
        DslJson<Object> dj = new DslJson<>(Settings.withRuntime());
        for (int i = 0; i < TEST_COUNT; ++i) {
            C c = dj.deserialize(C.class, bytes, bytes.length);
            v += c.a + c.c.a;
        }
        System.out.println("Dsl-Json: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
    }

    static void testJsoniter() { // jsoniter-0.9.23.jar和javassist-3.26.0-GA.jar总大小1.08MB
        long t = System.nanoTime(), v = 0;
        // 设置最快的模式,需要借助javassist库(javassist-3.26.0-GA.jar大小765KB)
        JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH);
        for (int i = 0; i < TEST_COUNT; ++i) {
            C c = JsonIterator.deserialize(bytes, C.class);
            v += c.a + c.c.a;
        }
        System.out.println("Jsoniter: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
    }

    static void testJason() throws Exception { // jason.jar大小25KB
        long t = System.nanoTime(), v = 0;
        Jason jason = new Jason();
        for (int i = 0; i < TEST_COUNT; ++i) {
            C c = jason.buf(bytes).parse(C.class);
            v += c.a + c.c.a;
        }
        System.out.println("   Jason: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
    }

    public static void main(String[] args) throws Exception {
        // 各测试5轮,排除预热影响,以最少时间为准
        for (int i = 0; i < 5; ++i)
            testJackson();
        for (int i = 0; i < 5; ++i)
            testFastJson();
        for (int i = 0; i < 5; ++i)
            testDslJson();
        for (int i = 0; i < 5; ++i)
            testJsoniter();
        for (int i = 0; i < 5; ++i)
            testJason();
    }
}

目前还只是初始版本(只有一千多行代码,支持JDK8及更高版本), Json的解析算法暂时只参考了RapidJSON的数字解析. 后续还会再学习其它实现中的优秀之处.

补充一些说明:

  • 上面的性能测试是在AMD的CPU上统计的, 之后在Intel的CPU上测出的结果有些出入, 主要是Jsoniter表现得更好了, 结果跟Jason很接近, 不得不佩服Jsoniter的性能.

  • 如果有人现在需要生产用途的Json库,推荐用Jsoniter,代码质量也比FastJson好不少, 可惜Jsoniter已经有一段时间没更新维护了, 不知道何时出1.0版本.

  • Jsoniter的代码编译使用了Javassist库, 但我认为可以直接调用JDK内置的编译, 做到完全无三方依赖.

  • Jason支持JDK8以上, 但推荐运行在JDK9以上. 因为Jason用了一些JDK9才有的优化, 而且一些底层调用在JDK9以上能更高效地运行.

  • Jason的数字解析参考了目前最快的RapidJSON的实现, 也做了少量Java特定的优化, 最终跟C++版本的性能差距不大了. 比Jsoniter的相关实现快一些.

  • Jason除了支持JSON标准之外, 还支持忽略多余的逗号, 支持不带引号的key. 其它扩展(如注释)的支持待考虑. 另外Jason对不标准的JSON有一定程度的容忍, 但这是快速解析的副作用,不能当做feature.另外Jason目前只支持UTF-8编码的字节数组作为输入.

  • Jason支持了动态确定类型的parser. 有两种方法, 一种见其中的Parser类(推荐), 另一种见其中的Pos类.

  • Jason用了很多JDK中Unsafe的调用, 这个是非公开API, 因此不支持Android.

  • Jason还缺乏充足的测试代码, 后续会逐渐补充.

  • Jason后续有打算做序列化功能. 但不确定何时完成.

  • Jason有打算增加文档, 如果有足够关注的话.

最后

上面都是自己整理好的!我就把资料贡献出来给有需要的人!顺便求一波关注,哈哈~各位小伙伴关注我后私信【Java】就可以免费领取哒

原文链接:zhuanlan.zhihu.com/p/108486101