MSON,让JSON序列化更快

问题

我们经常需要在主线程中读取一些配置文件或者缓存数据,最常用的结构化存储数据的方式就是将对象序列化为JSON字符串保存起来,这种方式特别简单而且可以和SharedPrefrence配合使用,因此应用广泛。但是目前用到的Gson在序列化JSON时很慢,在读取解析这些必要的配置文件时性能不佳,导致卡顿启动速度减慢等问题。

Gson的问题在哪里呢?笔者用AndroidStudio的profile工具分析了activity.onCreate方法的耗时情况。

图 1

图 2

如图1所示,可以发现Gson序列化占用了大部分的执行时间,从图2可以更直观地看到Gson.fromJson占用了61%的执行时间。分析Gson的源码可以发现,它在序列化时大量使用了反射,每一个field,每一个get、set都需要用反射,由此带来了性能问题。

如何优化

知道了性能的瓶颈之后,我们如何去修改呢?我能想到的方法就是尽量减少反射。

Android框架中由JSONObject来提供轻量级的JSON序列化工具,所以我选择用Android框架中的JSONObject来做序列化,然后手动复制到bean就可以去掉所有的反射。

我做了个简单的测试,分别用Gson和JSONObject的方式去序列化一个bean,看下各自速度如何。

使用JSONObject的实现方式如下:

public class Bean {public String key;public String title;public String[] values;public String defaultValue;public static Bean fromJsonString(String json) {try {JSONObject jsonObject = new JSONObject(json);Bean bean = new Bean();bean.key = jsonObject.optString("key");bean.title = jsonObject.optString("title");JSONArray jsonArray = jsonObject.optJSONArray("values");if (jsonArray != null && jsonArray.length() > 0) {int len = jsonArray.length();bean.values = new String[len];for (int i=0; i<len; ++i) {bean.values[i] = jsonArray.getString(i);}}bean.defaultValue = jsonObject.optString("defaultValue");return bean;} catch (JSONException e) {e.printStackTrace();}return null;}public static String toJsonString(Bean bean) {if (bean == null) {return null;}JSONObject jsonObject = new JSONObject();try {jsonObject.put("key", bean.key);jsonObject.put("title", bean.title);if (bean.values != null) {JSONArray array = new JSONArray();for (String str:bean.values) {array.put(str);}jsonObject.put("values", array);}jsonObject.put("defaultValue", bean.defaultValue);} catch (JSONException e) {e.printStackTrace();}return jsonObject.toString();}
}

测试代码:

private void test() {String a = "{\"key\":\"123\", \"title\":\"asd\", \"values\":[\"a\", \"b\", \"c\", \"d\"], \"defaultValue\":\"a\"}";Gson Gson = new Gson();Bean testBean = Gson.fromJson(a, new TypeToken<Bean>(){}.getType());long now = System.currentTimeMillis();for (int i=0; i<1000; ++i) {Gson.fromJson(a, new TypeToken<Bean>(){}.getType());}Log.d("time", "Gson parse use time="+(System.currentTimeMillis() - now));now = System.currentTimeMillis();for (int i=0; i<1000; ++i) {Bean.fromJsonString(a);}Log.d("time", "jsonobject parse use time="+(System.currentTimeMillis() - now));now = System.currentTimeMillis();for (int i=0; i<1000; ++i) {Gson.toJson(testBean);}Log.d("time", "Gson tojson use time="+(System.currentTimeMillis() - now));now = System.currentTimeMillis();for (int i=0; i<1000; ++i) {Bean.toJsonString(testBean);}Log.d("time", "jsonobject tojson use time="+(System.currentTimeMillis() - now));
}

测试结果

序列化方法GsonJSONObject
序列化耗时(ms)569
反序列化耗时(ms)977

执行1000次JSONObject,花费的时间是Gson的几十分之一。

工具

虽然JSONObject能够解决我们的问题,但在项目中有大量的存量代码都使用了Gson序列化,一处处去修改既耗费时间又容易出错,也不方便增加减少字段。

那么有没有一种方式在使用时和Gson一样简单且性能又特别好呢?

我们调研了Java的AnnotationProcessor(注解处理器),它能够在编译前对源码做处理。我们可以通过使用AnnotationProcessor为带有特定注解的bean自动生成相应的序列化和反序列化实现,用户只需要调用这些方法来完成序列化工作。

我们继承“AbstractProcessor”,在处理方法中找到有JsonType注解的bean来处理,代码如下:

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(JsonType.class);for (Element element : elements) {if (element instanceof TypeElement) {processTypeElement((TypeElement) element);}}return false;
}

然后生成对应的序列化方法,关键代码如下:

JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(fullClassName);
ClassModel classModel = new ClassModel().setModifier("public final").setClassName(simpleClassName);
......
JavaFile javaFile = new JavaFile();
javaFile.setPackageModel(new PackageModel().setPackageName(packageName)).setImportModel(new ImportModel().addImport(elementClassName).addImport("com.meituan.android.MSON.IJsonObject").addImport("com.meituan.android.MSON.IJsonArray").addImport("com.meituan.android.MSON.exceptions.JsonParseException").addImports(extension.getImportList())).setClassModel(classModel);List<? extends Element> enclosedElements = element.getEnclosedElements();
for (Element e : enclosedElements) {if (e.getKind() == ElementKind.FIELD) {processFieldElement(e, extension, toJsonMethodBlock, fromJsonMethodBlock);}
}
try (Writer writer = sourceFile.openWriter()) {writer.write(javaFile.toSourceString());writer.flush();writer.close();
}

为了今后接入别的字符串和JSONObject的转换工具,我们封装了IJSONObject和IJsonArray,这样可以接入更高效的JSON解析和格式化工具。

继续优化

继续深入测试发现,当JSON数据量比较大时用JSONObject处理会比较慢,究其原因是JSONObject会一次性将字符串读进来解析成一个map,这样会有比较大的内存浪费和频繁内存创建。经过调研Gson内部的实现细节,发现Gson底层有流式的解析器而且可以按需解析,可以做到匹配上的字段才去解析。根据这个发现我们将我们IJSONObject和IJsonArray换成了Gson底层的流解析来进一步优化我们的速度。

代码如下:

Friend object = new Friend();
reader.beginObject();
while (reader.hasNext()) {String field = reader.nextName();if ("id".equals(field)) {object.id = reader.nextInt();} else if ("name".equals(field)) {if (reader.peek() == JsonToken.NULL) {reader.nextNull();object.name = null;} else {object.name = reader.nextString();}} else {reader.skipValue();}
}
reader.endObject();

代码中可以看到,Gson流解析过程中我们对于不认识的字段直接调用skipValue来节省不必要的时间浪费,而且是一个token接一个token读文本流这样内存中不会存一个大的JSON字符串。

兼容性

兼容性主要体现在能支持的数据类型上,目前MSON支持了基础数据类型,包装类型、枚举、数组、List、Set、Map、SparseArray以及各种嵌套类型(比如:Map<String, Map<String, List<String[]>>>)。

性能及兼容性对比

我们使用一个比较复杂的bean(包含了各种数据类型、嵌套类型)分别测试了Gson、fastjson和MSON的兼容性和性能。

测试用例如下:

@JsonType
public class Bean {public Day day;public List<Day> days;public Day[] days1;@JsonField("filed_a")public byte a;public char b;public short c;public int d;public long e;public float f;public double g;public boolean h;@JsonField("filed_a1")public byte[] a1;public char[] b1;public short[] c1;public int[] d1;public long[] e1;public float[] f1;public double[] g1;public boolean[] h1;public Byte a2;public Character b2;public Short c2;public Integer d2;public Long e2;public Float f2;public Double g2;public Boolean h2;@JsonField("name")public String i2;public Byte[] a3;public Character[] b3;public Short[] c3;public Integer[] d3;public Long[] e3;public Float[] f3;public Double[] g3;public Boolean[] h3;public String[] i3;@JsonIgnorepublic String i4;public transient String i5;public static String i6;public List<String> k;public List<Integer> k1;public Collection<Integer> k2;public ArrayList<Integer> k3;public Set<Integer> k4;public HashSet<Integer> k5;// fastjson 序列化会崩溃所以忽略掉了,下同@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public List<int[]> k6;public List<String[]> k7;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public List<List<Integer>> k8;@JsonIgnorepublic List<Map<String, Integer>> k9;@JsonIgnorepublic Map<String, String> l;public Map<String, List<Integer>> l1;public Map<Long, List<Integer>> l2;public Map<Map<String, String>, String> l3;public Map<String, Map<String, List<String>>> l4;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false) public SparseArray<SimpleBean2> m1;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public SparseIntArray m2;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public SparseLongArray m3;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public SparseBooleanArray m4;public SimpleBean2 bean;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public SimpleBean2[] bean1;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public List<SimpleBean2> bean2;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public Set<SimpleBean2> bean3;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public List<SimpleBean2[]> bean4;@com.alibaba.fastjson.annotation.JSONField(serialize = false, deserialize = false)public Map<String, SimpleBean2> bean5;
}

测试发现:

  1. Gson的兼容性最好,能兼容几乎所有的类型,MSON其次,fastjson对嵌套类型支持比较弱。
  2. 性能方面MSON最好,Gson和fastjson相当。

测试结果如下:

序列化方法MSONGsonfastjson
序列化耗时(ms)204755
反序列化耗时(ms)12043

方法数

MSON本身方法数很少只有60个,在使用时会对每一个标注了JsonType的Bean生成2个方法,分别是:

public String toJson(Bean bean) {...}              // 1
public Bean fromJson(String data) {...}            // 2

另外MSON不需要对任何类做keep处理。

MSON使用方法

下面介绍MSON的使用方法,流程特别简单:

1. 在Bean上加注解

@JsonType
public class Bean {public String name;public int age;@JsonField("_desc")public String description;  //使用JsonField 标注字段在json中的keypublic transient boolean state; //使用transient 不会被序列化@JsonIgnorepublic int state2; //使用JsonIgnore注解 不会被序列化}

2. 在需要序列化的地方

MSON.fromJson(json, clazz); // 反序列化
MSON.toJson(bean); // 序列化

总结

本文介绍了一种高性能的JSON序列化工具MSON,以及它的产生原因和实现原理。目前我们已经有好多性能要求比较高的地方在使用,可以大幅的降低JSON的序列化时间。

招聘信息

美团平台客户端技术团队长期招聘技术专家,有兴趣的同学可以发送简历到:fangjintao#meituan.com。
详情请点击:详细JD

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/478583.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

屠榜各大CV任务!「百度顶会论文复现营」携Swin Transformer来袭!

目标检测刷到58.7 AP&#xff01;实例分割刷到51.1 Mask AP&#xff01;&#xff01;语义分割在ADE20K上刷到53.5 mIoU&#xff01;&#xff01;&#xff01;......Swin Transformer持续屠榜各大CV任务&#xff0c;并且均名列前茅&#xff01;通过分层体系结构&#xff0c;带来…

百度任务型对话系统小记

意图扩展阅读&#xff1a; 古月哲亭: AAAI 2021 | 清华提出深度对齐聚类用于新意图发现&#xff1a;https://mp.weixin.qq.com/s/9dNs8TTERPdxmrVc3tF1zw 相关项目地址&#xff1a;https://github.com/thuiar/OKD-Reading-List 古月哲亭: 意图知识图谱的构建与应用&#xff1a…

论文浅尝 - EMNLP2020 | 跨媒体关键词预测: 多模态多头注意力和图像文本的统一框架...

论文笔记整理&#xff1a;柏超宇&#xff0c;东南大学硕士。文章链接&#xff1a;https://arxiv.org/pdf/2011.01565.pdf来源&#xff1a;EMNLP 2020动机社交媒体每天都会产生大量的内容。为了帮助用户快速捕捉所需内容&#xff0c;关键词预测受到越来越多的关注。尽管如此&…

从实际案例聊聊Java应用的GC优化

当Java程序性能达不到既定目标&#xff0c;且其他优化手段都已经穷尽时&#xff0c;通常需要调整垃圾回收器来进一步提高性能&#xff0c;称为GC优化。但GC算法复杂&#xff0c;影响GC性能的参数众多&#xff0c;且参数调整又依赖于应用各自的特点&#xff0c;这些因素很大程度…

LeetCode 162. 寻找峰值(二分查找)

1. 题目 峰值元素是指其值大于左右相邻值的元素。 给定一个输入数组 nums&#xff0c;其中 nums[i] ≠ nums[i1]&#xff0c;找到峰值元素并返回其索引。 数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回任何一个峰值所在位置即可。 你可以假设 nums[-1] n…

谷歌:CNN击败Transformer,有望成为预训练界新霸主!LeCun却沉默了...

文 | ????????????????这几年&#xff0c;大家都说深度学习进入了预训练时代。作为一个入行不久的小白&#xff0c;我一直以为各类基于 Transformers 结构的预训练模型是 NLP 的巨大里程碑&#xff0c;CNN、RNN 老矣&#xff0c;只配作为手下败将。大家的文章似…

新词发现简介

原文链接&#xff1a;https://blog.csdn.net/weixin_43378396/article/details/103848628 新词发现是 NLP 的基础任务之一&#xff0c;通过对已有语料进行挖掘&#xff0c;从中识别出新词。新词发现也可称为未登录词识别&#xff0c;严格来讲&#xff0c;新词是指随时代发展而新…

论文浅尝 - AAAI2020 | 利用自然语言推断生成人称一致的对话

链接&#xff1a; https://arxiv.org/pdf/1911.05889.pdf动机虽然最近几年通过利用社交网络上大量人人交互数据训练开放域对话模型取得了很大的成功&#xff0c;但是这些数据驱动的对话系统仍然无法很自然的与人类对话&#xff0c;其中的一个主要问题就是对话系统缺乏一致的角色…

智能投放系统之场景分析最佳实践

美团点评作为业内最大的O2O的平台&#xff0c;以短信/push作为运营手段触达用户的量级巨大&#xff0c;每日数以千万计。 美团点评线上存在超过千万的POI&#xff0c;覆盖超过2000城市、2.5万个后台商圈。在海量数据存在的前提下&#xff0c;实时投放的用户在场景的选择上存在一…

7个提升PyTorch性能的技巧

文 | William Falcon源 | AI公园在过去的10个月里&#xff0c;在PyTorch Lightning工作期间&#xff0c;团队和我已经接触过许多结构PyTorch代码的风格&#xff0c;我们已经发现了一些人们无意中引入瓶颈的关键地方。我们非常小心地确保PyTorch Lightning不会对我们为你自动编写…

论文浅尝 - EMNLP2020 | 基于规则引导的协作 agent 知识图谱推理学习

论文笔记整理&#xff1a;叶橄强&#xff0c;浙江大学在读硕士&#xff0c;研究方向为知识图谱的表示学习和预训练。来源&#xff1a;EMNLP 2020现有的大多数基于行走的模型通过在提供可解释的决策的同时获得良好的性能&#xff0c;在知识图谱推理中显示出其优势。但在遍历过程…

Shield——开源的移动端页面模块化开发框架

一直以来&#xff0c;如何能更高效地开发与维护页面是Android与iOS开发同学最主要的工作和最关心的问题。随着业务的不断发展&#xff0c;根据特定业务场景产生的定制化需求变得越来越多。单一页面往往需要根据不同业务、不同场景甚至不同用户展示不同的内容。在这样的背景下&a…

1年排名前进13位 ,这个论题成顶会新宠!

写过论文的同学都知道&#xff0c;写久了真的会头秃&#xff0c;其中耗发量最高的当属论题和创新点。今天分享一套方法&#xff0c;这个方法已经帮助近3000位同学成功发&#xff08;拯&#xff09;表&#xff08;救&#xff09;论&#xff08;头&#xff09;文&#xff08;发&a…

LeetCode 50. Pow(x, n)(二分查找)

文章目录1. 题目2. 二分查找2.1 递归2.2 循环1. 题目 实现 pow(x, n) &#xff0c;即计算 x 的 n 次幂函数。 示例 输入: 2.00000, 10 输出: 1024.00000 示例 输入: 2.00000, -2 输出: 0.25000 解释: 2-2 1/22 1/4 0.25 说明: -100.0 < x < 100.0 n 是 32 位有符号…

OpenKG 祝大家 2021 新年快乐 —「2020 精选文章汇编」

过去的一年是不寻常的一年&#xff0c;虽然疫情改变了所有人的生活&#xff0c;但是它并没有击垮我们。这一年&#xff0c;大家依旧保持着开源开放的精神&#xff0c;持续地分享着知识图谱领域的技术动态、应用实践&#xff0c;同学们也持续不断地输出优质的论文笔记。OpenKG 继…

智能分析最佳实践——指标逻辑树

所有业务都会面对“为什么涨、为什么降、原因是什么&#xff1f;”这种简单粗暴又不易定位的业务问题。为了找出数据发生异动的原因&#xff0c;业务人员会通过使用多维查询、dashboard等数据产品锁定问题&#xff0c;再辅助人工分析查找问题原因&#xff0c;这个过程通常需要一…

论文浅尝 - EMNLP2020 | 图结构对于多跳问答而言必要吗?

笔记整理 | 陈卓&#xff0c;浙江大学计算机科学与技术系&#xff0c;博士研究生研究方向 | 知识图谱&#xff0c;图神经网络&#xff0c;多模态论文链接&#xff1a;https://www.aclweb.org/anthology/2020.emnlp-main.583.pdf发表会议&#xff1a;EMNLP 2020背景提要抽取式阅…

为什么每次有人大声通电话时,我就很烦躁...

文 | Chaos编 | 小戏不知你是否有过这样的体验&#xff0c;当你周围有人在大声讲电话时&#xff0c;你会不自觉的感觉到烦躁。为什么呢&#xff1f;有一种委婉的说法是因为你听到了不完整的对话。直白点说其实就是讲电话的人通过放大声音强行让你接收了他说的信息&#xff0c;但…

LeetCode 367. 有效的完全平方数(二分查找)

1. 题目 给定一个正整数 num&#xff0c;编写一个函数&#xff0c;如果 num 是一个完全平方数&#xff0c;则返回 True&#xff0c;否则返回 False。 说明&#xff1a;不要使用任何内置的库函数&#xff0c;如 sqrt。 示例 1&#xff1a; 输入&#xff1a;16 输出&#xff1…