FCon7折倒计时最后一周:日程已上线70%!查看详情>>> 了解详情
写点什么

MSON,让 JSON 序列化更快

  • 2020-02-25
  • 本文字数:5589 字

    阅读完需:约 18 分钟

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 来处理,代码如下:


@Overridepublic 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 的兼容性和性能。


测试用例如下:


@JsonTypepublic 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;
@JsonIgnore public 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;
@JsonIgnore public List<Map<String, Integer>> k9; @JsonIgnore public 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) {...}       // 1public Bean fromJson(String data) {...}      // 2
复制代码


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

MSON 使用方法

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

1. 在 Bean 上加注解

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

2. 在需要序列化的地方

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

总结

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


2020-02-25 20:32833

评论

发布
暂无评论
发现更多内容

国泰君安期货新一代国产业务系统上线 首次使用国产分布式数据库TDSQL

Geek_2d6073

大学云桌面系统如何部署,有哪些优势

青椒云云电脑

云桌面 云桌面系统

云服务器哪家最便宜

青椒云云电脑

云服务器

信创国产堡垒机怎么样?哪家好?电话多少?

行云管家

云计算 信创 国产化 云堡垒机

Java基础面试题 【二】JUC

派大星

Java 面试题

【小程序压力测试】一文教你没有文档玩转小程序压力测试

优测云服务平台

小程序 性能测试 压力测试、

2024广州国际聚四氟乙烯制品及材料展览会

吹吹晚风

TDD、BDD、ATDD都是什么、有什么区别?(上)

禅道项目管理

Photoshop 2024 (ps2024) for Mac v25.0正式版/25.1beta完整激活版

mac

图像处理软件 苹果mac Windows软件 Photoshop 2024

该选私有云还是公有云?全面解析企业上云如何选

青椒云云电脑

云服务 私有云 云桌面

REST API设计原则:构建可扩展、易维护的 API

高端章鱼哥

RESTful API REST API

袋鼠云产品功能更新报告07期|智能、高效、安全,一个都不能少!

袋鼠云数栈

大数据 数据中台 产品更新

2024广州国际导电防静电塑料及导电橡胶展览会

吹吹晚风

2024广州国际功能薄膜与包装产业展览会

吹吹晚风

基于Java开发的数字化询价招标采购系统(SRM系统源码)

金陵老街

spring-boot

2024中国广州国际氟塑料及应用设备展览会

吹吹晚风

Performance of Maxon WiFi 6 Industrial Access Point

wifi6module

行于“云”上,“翼”路顺畅!

天翼云开发者社区

云计算 云服务

私有云的优缺点是什么?与公有云的区别

青椒云云电脑

云桌面

护航政务“云上安全”,天翼云打造自主可控政务云能力体系!

天翼云开发者社区

云计算 网络安全 云服务

一文详解GPU虚拟化云桌面解决方案

青椒云云电脑

云桌面 云桌面解决方案

如何使用ChatGPT构建一个Web应用程序?

互联网工科生

应用程序 ChatGPT AI编程

华为云CodeArts Check代码检查服务用户声音反馈集锦(7)

华为云PaaS服务小智

云计算 软件开发 华为云 代码检查

五项大奖、三项评估!为行业数字化转型发展注智赋能!

天翼云开发者社区

云计算 云服务

BIM对电脑配置要求高 云电脑了解一下

青椒云云电脑

云电脑

2024中国(广州) 国际不干胶标签展览会

吹吹晚风

【运维 Pro】时序场景实践与原理 - 2. 宽表,窄表与 JSON 字段

YMatrix 超融合数据库

json 时序 时序数据 超融合数据库 YMatrix

云桌面如何助力校园云办公 老师这么说

青椒云云电脑

云桌面

新手入门:买完云服务器怎么搭建网站?

青椒云云电脑

云电脑

2023年国产堡垒机就选行云!8大理由看这里!

行云管家

网络安全 堡垒机 国产化 国产

BIM遇上GPU云 设计师不换电脑的秘密

青椒云云电脑

cpu

  • 扫码添加小助手
    领取最新资料包
MSON,让JSON序列化更快_文化 & 方法_美团技术团队_InfoQ精选文章