Antlr实战之JSON解析器slowjson
最近一直在学习编译原理,然后就了解到了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。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
如何启用SAP Cloud Platform的mobile服务
在CloudFoundry环境的Service Market里找到Mobile Services,启用之后, 点击Support,进入mobile services的控制台: 我们要在Neo环境的WebIDE里控制CloudFoundry环境的mobile服务,因此需要创建Destination,指向CloudFoundry mobile services的admin url: Neo环境里创建一个Destination: 将mobile service控制台里的admin API url拷贝下来, 粘贴到Neo Destination的url字段里: 确保Check Connection连接成功。 本文来自云栖社区合作伙伴“汪子熙”,了解相关信息可以关注微信公众号"汪子熙"。
- 下一篇
2020 年,哪一门计算机技能最当红?
2020 年,哪一门计算机技能最当红? 为了研究这件事,美国招聘网站 Indeed 做了一项调查,统计了 2014 年到 2019 年五年间发布在自己网站上的数百万个美国地区的岗位数据,这些岗位共包含了 571 项计算机技能的关键词。 结论很有参考意义,比如说: 根据数据显示,SQL 和 Java 是雇主想要的前两项技术技能。; Python 是第三大最常见的技能,其强劲增长部分归功于数据科学工作; 排名第六的亚马逊网络服务(AWS)的增长更为惊人。 近两年来 Python 的爆火我们都能感受到,但 AWS 为什么大幅增长 418%?Java 和 C 分别排在什么位置、变化趋势如何? 通过这份 Indeed.com 发布的数百万美国技术工作,可以找到一些答案。 技术能力社会需求量 TOP 20 如上表所示,在雇主想要的所有技术技能中,SQL 虽然逐年下降,但仍然排名第一,大概占比 22%。 Java 老当益壮,位列第二,并保持着缓慢的增长。 Python 位居第三,但五年间取得了 123% 的增长。Python 受欢迎程度的上升也反映了工作的新组合,其中包括数据科学家和数据工程师等强劲...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Hadoop3单机部署,实现最简伪集群
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7安装Docker,走上虚拟化容器引擎之路