Antlr实战之JSON解析器slowjson

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 把这个文件保存成 JSON.g4,然后执行下面命令,当然前提是你得正确安装antlr4。

最近一直在学习编译原理,然后就了解到了antlr4这个强大的工具,antlr的全称是(Another Tool for Language Recognition),是一款很强大的词法和语法分析工具,虽然是用java写成的,但它也能生成c++、go……等语言的代码。它的主要作用就是你可以用巴科斯范式来描述语法规则,然后它帮你生成对应的解析器。

大家都知道实践是最好的学习方式,要快速深刻地理解antlr的操作和相关接口就不得不找一个练手的东西。回想到去年连续报安全漏洞的fastjson,所以我准备霍霍一下json解析器。咱写不出来比fastjson更快、bug更少、更安全的json解析器,难道还写不出来一个bug更多、更慢、更不安全的解析器吗,正面拼不赢咱反其道而行。

为了对标阿里的fastjson,我给它起名 slowjson,源码已在github slowjson 欢迎star。为了推广slowjson,我都想好广告词了。


你想升职加薪吗?

你想拿年终奖吗?

你想成为同事眼中的性能优化小能手吗?

今天用slowjson,年底做性能优化换回fastjson,十倍性能不是梦,升职加薪准能成。


解析JSON字符串

说这么多进入正题,json解析器该怎么写?实际上你并不需要自己动手写词法分析器、语法分析器……,今天的主角antlr都会帮你生成,你只需要用巴科斯范式把json的语法规则描述清楚就行了,这份描述你可以直接在json.org找到,在antlr的github代码库里也有,二者看起来稍有差别,json官网的规则更详细些。这里我直接用antlr提供的规则描述。


grammar JSON;
json
   : value
   ;
obj
   : '{' pair (',' pair)* '}'
   | '{' '}'
   ;
pair
   : STRING ':' value
   ;
array
   : '[' value (',' value)* ']'
   | '[' ']'
   ;
value
   : STRING
   | NUMBER
   | obj
   | array
   | 'true'
   | 'false'
   | 'null'
   ;
STRING
   : '"' (ESC | SAFECODEPOINT)* '"'
   ;
fragment ESC
   : '\\' (["\\/bfnrt] | UNICODE)
   ;
fragment UNICODE
   : 'u' HEX HEX HEX HEX
   ;
fragment HEX
   : [0-9a-fA-F]
   ;
fragment SAFECODEPOINT
   : ~ ["\\\u0000-\u001F]
   ;
NUMBER
   : '-'? INT ('.' [0-9] +)? EXP?
   ;
fragment INT
   : '0' | [1-9] [0-9]*
   ;
// no leading zeros
fragment EXP
   : [Ee] [+\-]? INT
   ;
// \- since - means "range" inside [...]
WS
   : [ \t\n\r] + -> skip
   ;

把这个文件保存成 JSON.g4,然后执行下面命令,当然前提是你得正确安装antlr4。


antlr4 JSON.g4  -no-listener -package xyz.xindoo.slowjson

这个时候antlr就会帮你生成json的词法分析器JSONLexer.java和语法分析器JSONParser.java。


private static String jsonStr = "{\"key1\":\"value1\",\"sub\":{\"subkey\":\"subvalue1\"}}"; 
    public static JSONParser.ObjContext parse() {
        JSONLexer lexer = new JSONLexer(CharStreams.fromString(jsonStr));
        CommonTokenStream tokens = new CommonTokenStream(lexer);  //生成token 
        JSONParser parser = new JSONParser(tokens);
        JSONParser.ObjContext objCtx = parser.obj(); // 将token转化为抽象语法树(AST) 
        return new objCtx;
    }


实际上你只需要写上面这么多代码,就可以完成对一个jsonStr的解析,不过这里解析后的结果是antlr内部封装的抽象语法树,利用antlr的idea插件,我们可以将解析后的AST可视化出来, "{“key1”:“value1”,“sub”:{“subkey”:“subvalue1”}}"的语法树长下面这样。



JSON字符到JSONObject

虽然已经完成了json字符串的解析,但如果你想像fastjson那样使用,你还得完成对语法树节点到JSONObject的转化。antlr根据语法规则,已经自动帮你生成了每个节点类型,实际上你只需要遍历整个树,然后把每个节点转化为JSONObject或者k-v对就可以了。


package xyz.xindoo.slowjson;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class JSONObject {
    private Map<String, Object> map;
    public JSONObject() {
        this.map = new HashMap<>();
    }
    protected JSONObject(JSONParser.ObjContext objCtx) {
        this.map = new HashMap<>();
        for (JSONParser.PairContext pairCtx: objCtx.pair()) {
            String key = pairCtx.STRING().getText();
            map.put(key.substring(1, key.length()-1), pairCtx.value());
        }
    }
    public JSONObject getJSONObject(String key) {
        JSONParser.ValueContext value = (JSONParser.ValueContext)map.get(key);
        if (value == null) {
            return null;
        }
        return new JSONObject(value.obj());
    }
    public String getString(String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (JSONParser.ValueContext.class.isInstance(value)) {
            JSONParser.ValueContext ctx = (JSONParser.ValueContext)value;
            String newValue = ctx.STRING().getText();
            map.put(key, newValue.substring(1, newValue.length()-1));
        }
        return (String)map.get(key);
    }
    public int getInt(String key) {
        String value = getString(key);
        if (value == null || "".equals(value)) {
            return 0;
        }
        return Integer.parseInt(value);
    }
    public long getLong(String key) {
        String value = getString(key);
        if (value == null || "".equals(value)) {
            return 0L;
        }
        return Long.parseLong(value);
    }
    public double getDouble(String key) {
        String value = getString(key);
        if (value == null || "".equals(value)) {
            return 0.0;
        }
        return Double.parseDouble(value);
    }
    public JSONArray getJSONArray(String key) {
        JSONParser.ValueContext value = (JSONParser.ValueContext)map.get(key);
        if (value == null) {
            return null;
        }
        return new JSONArray(value.array());
    }
    public void put(String key, Object object) {
        map.put(key, object);
    }
    public static JSONObject parseObject(String text) {
        JSONLexer lexer = new JSONLexer(CharStreams.fromString(text));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JSONParser parser = new JSONParser(tokens);
        JSONParser.ObjContext objCtx = parser.obj();
        return new JSONObject(objCtx);
    }
    public static JSONArray parseArray(String text) {
        if (text == null) {
            return null;
        }
        JSONArray array = JSONArray.parseArray(text);
        return array;
    }
}

代码中我并没有遍历整个AST并将其转化为JSONObject,而是等到需要的时候再转,实现起来比较方便。看到这里有没有发现slowjson的API和fastjson的很像! 没错,我就是抄的fastjson,而且我还没抄全。。。


性能测试

接下来做个很随便的性能测试,我随便找了个json字符串,并拉来了slowjson的几个主要竞争对手 fastjson、jackson、gson,测试结果如下:


Benchmark       Mode  Cnt       Score   Error  Units
Test.fastjson  thrpt    2  235628.511          ops/s
Test.gson      thrpt    2  237975.534          ops/s
Test.jackson   thrpt    2  212453.073          ops/s
Test.slowjson  thrpt    2   29905.109          ops/s


性能只差一个数量级,没我预期的慢……这这么行呢,加上随机自旋……


private static void randomSpin() {
        Random random = new Random();
        int nCPU = Runtime.getRuntime().availableProcessors();
        int spins = (random.nextInt()%8 + nCPU) * SPIN_UNIT;
        while (spins > 0) {
            spins--;
            float a = random.nextFloat();
        }
    }


然后在所有get的方法里先调用一次随机自旋,消耗掉cpu。再来测试下性能。


Benchmark       Mode  Cnt       Score   Error  Units
Test.fastjson  thrpt    2  349994.543          ops/s
Test.gson      thrpt    2  318087.884          ops/s
Test.jackson   thrpt    2  244393.573          ops/s
Test.slowjson  thrpt    2    2681.164          ops/s


嗯~ 这次差两个量级了,达到了我生产环境的性能标准,可以上线了……


JSONObject到JSON字符串

wait wait 桥都麻袋,目前只实现了json字符串到JSONObject的转换,没有实现从JSONObject到json字符串的转化,功能不完整啊。不过这个也简单,我们按照JSONObject里对象的层次,递归地来做toSting,代码如下。


@Override
    public String toString() {
        return toJSONString();
    }
    public String toJSONString() {
        StringBuilder sb = new StringBuilder();
        List<String> list = new ArrayList<>(map.size());
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object object = entry.getValue();
            String value = null;
            if (String.class.isInstance(object)) {
                value = "\"" + object.toString() + "\"";
            } else if (JSONObject.class.isInstance(object)) {
                value = object.toString();
            } else if (JSONArray.class.isInstance(object)) {
                value = object.toString();
            } else {
                value = ((JSONParser.ValueContext)object).getText();
            }
            list.add("\"" + key + "\":" + value);
        }
        sb.append("{");
        sb.append(String.join(",", list));
        sb.append("}");
        return sb.toString();
    }

JSONArray

上面始终没有提到JSONArray,其实JSONArray也是JSON中重要组成部分,之所以没提是因为JSONArray和JSONObject的实现思路是非常相似的,而且简单多了,我的封装如下。


package xyz.xindoo.slowjson;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class JSONArray {
    private final List<JSONObject> list;
    public JSONArray() {
        this.list = new ArrayList<>();
    }
    public JSONArray(List<JSONObject> list) {
        this.list = new ArrayList<>(list.size());
        this.list.addAll(list);
    }
    protected JSONArray(JSONParser.ArrayContext arrayCtx) {
        this.list = arrayCtx.value()
                            .stream()
                            .map(valueContext -> new JSONObject(valueContext.obj()))
                            .collect(Collectors.toList());
    }
    public static JSONArray parseArray(String text) {
        JSONLexer lexer = new JSONLexer(CharStreams.fromString(text));
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        JSONParser parser = new JSONParser(tokens);
        JSONParser.ArrayContext arrayCtx = parser.array();
        return new JSONArray(arrayCtx);
    }
    public JSONObject getJSONObject(int index) {
        return list.get(index);
    }
    public void add(JSONObject jsonObject) {
        list.add(jsonObject);
    }
    @Override
    public String toString() {
        return toJSONString();
    }
    public String toJSONString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        List<String> strList = list.stream().map(JSONObject::toString).collect(Collectors.toList());
        sb.append(String.join(",", strList));
        sb.append("]");
        return sb.toString();
    }
}


Todo

上传至maven中心仓库,方便大家冲KPI,嘿嘿嘿。

完善API,虽然抄了fastjson的api,但确实没抄全。

完善类型,json规范里其实是支持null, boolean, 数字类型的,我这图简单都用了String类型。

完善Excption,目前如果抛Exception都是抛的antlr的,会对用户有误导作用。

增加控制随机自旋的API,性能控制交于用户。

实际上列Todo是为了让slowjson看起来像个项目,至于做不做就随缘了,毕竟不完美才是slowjson最大的特点。。。。


最后所有源码已上传至github slowjson ,欢迎star。

目录
相关文章
|
24天前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
72 13
|
20天前
|
物联网 调度 vr&ar
鸿蒙HarmonyOS应用开发 |鸿蒙技术分享HarmonyOS Next 深度解析:分布式能力与跨设备协作实战
鸿蒙技术分享:HarmonyOS Next 深度解析 随着万物互联时代的到来,华为发布的 HarmonyOS Next 在技术架构和生态体验上实现了重大升级。本文从技术架构、生态优势和开发实践三方面深入探讨其特点,并通过跨设备笔记应用实战案例,展示其强大的分布式能力和多设备协作功能。核心亮点包括新一代微内核架构、统一开发语言 ArkTS 和多模态交互支持。开发者可借助 DevEco Studio 4.0 快速上手,体验高效、灵活的开发过程。 239个字符
199 13
鸿蒙HarmonyOS应用开发 |鸿蒙技术分享HarmonyOS Next 深度解析:分布式能力与跨设备协作实战
|
19天前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
130 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
4天前
|
数据采集 XML API
深入解析BeautifulSoup:从sohu.com视频页面提取关键信息的实战技巧
深入解析BeautifulSoup:从sohu.com视频页面提取关键信息的实战技巧
|
11天前
|
JSON JavaScript 前端开发
一次采集JSON解析错误的修复
两段采集来的JSON格式数据存在格式问题,直接使用PHP的`json_decode`会报错。解决思路包括:1) 手动格式化并逐行排查错误;2) 使用PHP-V8JS扩展在JavaScript环境中解析。具体方案一是通过正则表达式和字符串替换修复格式,方案二是利用V8Js引擎执行JS代码并返回JSON字符串,最终实现正确解析。 简介: 两段采集的JSON数据因掺杂JavaScript代码导致PHP解析失败。解决方案包括手动格式化修复和使用PHP-V8JS扩展在JavaScript环境中解析,确保JSON数据能被正确处理。
|
15天前
|
安全 API 数据安全/隐私保护
速卖通AliExpress商品详情API接口深度解析与实战应用
速卖通(AliExpress)作为全球化电商的重要平台,提供了丰富的商品资源和便捷的购物体验。为了提升用户体验和优化商品管理,速卖通开放了API接口,其中商品详情API尤为关键。本文介绍如何获取API密钥、调用商品详情API接口,并处理API响应数据,帮助开发者和商家高效利用这些工具。通过合理规划API调用策略和确保合法合规使用,开发者可以更好地获取商品信息,优化管理和营销策略。
|
1月前
|
数据采集 DataWorks 搜索推荐
阿里云DataWorks深度评测:实战视角下的全方位解析
在数字化转型的大潮中,高效的数据处理与分析成为企业竞争的关键。本文深入评测阿里云DataWorks,从用户画像分析最佳实践、产品体验、与竞品对比及Data Studio公测体验等多角度,全面解析其功能优势与优化空间,为企业提供宝贵参考。
112 13
|
29天前
|
数据采集 存储 JavaScript
网页爬虫技术全解析:从基础到实战
在信息爆炸的时代,网页爬虫作为数据采集的重要工具,已成为数据科学家、研究人员和开发者不可或缺的技术。本文全面解析网页爬虫的基础概念、工作原理、技术栈与工具,以及实战案例,探讨其合法性与道德问题,分享爬虫设计与实现的详细步骤,介绍优化与维护的方法,应对反爬虫机制、动态内容加载等挑战,旨在帮助读者深入理解并合理运用网页爬虫技术。
|
1月前
|
存储 监控 调度
云服务器成本优化深度解析与实战案例
本文深入探讨了云服务器成本优化的策略与实践,涵盖基本原则、具体策略及案例分析。基本原则包括以实际需求为导向、动态调整资源、成本控制为核心。具体策略涉及选择合适计费模式、优化资源配置、存储与网络配置、实施资源监控与审计、应用性能优化、利用优惠政策及考虑多云策略。文章还通过电商、制造企业和初创团队的实际案例,展示了云服务器成本优化的有效性,最后展望了未来的发展趋势,包括智能化优化、多云管理和绿色节能。
|
2月前
|
自然语言处理 编译器 Linux

热门文章

最新文章

推荐镜像

更多