当前位置: 首页 > 科技观察

MSON让JSON序列化更快

时间:2023-03-14 17:23:29 科技观察

问题我们经常需要在主线程读取一些配置文件或者缓存数据。以结构化方式存储数据的最常见方式是将对象序列化为JSON字符串并保存。这个方法很简单,可以和SharedPrefrence结合使用,所以被广泛使用。但是目前使用的Gson在序列化JSON时速度很慢,在读取和解析这些必要的配置文件时性能很差,导致启动速度变慢卡顿等问题。Gson的问题在哪里?笔者使用AndroidStudio的profile工具来分析activity.onCreate方法的耗时情况。如图1所示,可以发现Gson序列化占用了大部分的执行时间。从图2可以更直观的看出Gson.fromJson占用了61%的执行时间。分析Gson的源码可以看出,它在序列化过程中大量使用了反射。每个字段,每个get和set都需要用到反射,这就带来了性能问题。如何优化知道性能瓶颈后,我们该如何修改呢?我能想到的方法是尽量减少反射。Android框架中的JSONObject提供了一个轻量级的JSON序列化工具,所以我选择使用Android框架中的JSONObject进行序列化,然后手动复制到bean中,去掉所有的反射。我做了一个简单的测试,使用Gson和JSONObject序列化一个bean,看看它们有多快。使用JSONObject的实现方式如下:publicclassBean{publicStringkey;publicStringtitle;publicString[]values;publicStringdefaultValue;publicstaticBeanfromJsonString(Stringjson){try{JSONObjectjsonObject=newJSONObject(json);Beanbean=newBean();bean.key=jsonObject.optString("key");bean.title=jsonObject.optString("title");JSONArrayjsonArray=jsonObject.optJSONArray("values");if(jsonArray!=null&&jsonArray.length()>0){intlen=jsonArray.length();bean.values=newString[len];for(inti=0;i(){}.getType());longnow=System.currentTimeMillis();for(inti=0;i<1000;++i){Gson.fromJson(a,newTypeToken(){}.getType());}Log.d("time","Gsonparseusetime="+(System.currentTimeMillis()-now));now=System.currentTimeMillis();for(inti=0;i<1000;++i){Bean.fromJsonString(a);}Log.d("time","jsonobjectparseusetime="+(System.currentTimeMillis()-now));now=System.currentTimeMillis();for(inti=0;我<1000;++i){Gson.toJson(testBean);}Log.d("time","Gsontojsonusetime="+(System.currentTimeMillis()-now));now=System.currentTimeMillis();for(inti=0;i<1000;++i){Bean.toJsonString(testBean);}Log.d("time","jsonobjecttojsonusetime="+(System.currentTimeMillis()-now));}测试结果执行JSONObject1000次,所花的时间是Gson工具的零点几。虽然JSONObject可以解决我们的问题,但是项目中有大量的存量代码使用了Gson序列化。费时费力,容易出错,不方便到处修改。增加和减少字段。那么有没有一种使用方法像Gson一样简单,性能又特别好呢?我们研究了Java的AnnotationProcessor(注解处理器),它可以在编译前对源代码进行处理。我们可以使用AnnotationProcessor为带有特定注解的bean自动生成相应的序列化和反序列化实现,用户只需要调用这些方法即可完成序列化工作。我们继承“AbstractProcessor”,找到处理方法中有JsonType注解的bean进行处理。代码如下:@Overridepublicbooleanprocess(Setset,RoundEnvironmentroundEnvironment){Setelements=roundEnvironment.getElementsAnnotatedWith(JsonType.class);for(Elementelement:elements){if(elementinstanceofTypeElement){processTypeElement((TypeElement)element);}}returnfalse;}然后生成对应的序列化方法,关键代码如下:JavaFileObjectsourceFile=processingEnv.getFiler().createSourceFile(fullClassName);ClassModelclassModel=newClassModel().setModifier("publicfinal")。setClassName(simpleClassName);......JavaFilejavaFile=newJavaFile();javaFile.setPackageModel(newPackageModel().setPackageName(packageName)).setImportModel(newImportModel().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);ListenclosedElements=element.getEnclosedElements();for(Elemente:enclosedElements){if(e.getKind()==ElementKind.FIELD){processFieldElement(e,extension,toJsonMethodBlock,fromJsonMethodBlock);}}try(Writerwriter=sourceFile.openWriter()){writer.write(javaFile.toSourceString());writer.flush();writer.close();}为将来访问For其他字符串和JSONObject转换工具,我们封装了IJSONObject和IJsonArray,可以接入更高效的JSON解析格式化工具。继续优化,继续深入测试。发现当JSON数据量比较大的时候,用JSONObject处理会比较慢,原因是JSONObject会一次性读入字符串解析成map,会造成比较大的内存浪费和频繁记忆创造。调查了Gson内部的实现细节,发现Gson底层有流式解析器,可以按需解析,只解析匹配的字段。根据这个发现,我们将我们的IJSONObject和IJsonArray换成了Gson的底层流分析,进一步优化了我们的速度。代码如下:Friendobject=newFriend();reader.beginObject();while(reader.hasNext()){Stringfield=reader.nextName();if("id".equals(field)){object.id=读者。nextInt();}elseif("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,这样一个大的JSON串不会存储在内存中。兼容性兼容性主要体现在支持的数据类型上。目前MSON支持基本数据类型、封装类型、枚举、数组、List、Set、Map、SparseArray以及各种嵌套类型(如:Map>>)。性能和兼容性对比我们使用了一个比较复杂的bean(包括各种数据类型和嵌套类型)分别测试了Gson、fastjson和MSON的兼容性和性能。测试用例如下:@JsonTypepublicclassBean{publicDayday;publicListdays;publicDay[]days1;@JsonField("filed_a")publicbytea;publiccharb;publicshortc;publicintd;publiclonge;publicfloatf;publicdoubleg;publicbooleanh;@JsonField("filed_a1")publicbyte[]a1;publicchar[]b1;publicshort[]c1;publicint[]d1;publiclong[]e1;publicfloat[]f1;publicdouble[]g1;publicboolean[]h1;publicBytea2;publicCharacterb2;publicShortc2;publicIntegerd2;publicLonge2;publicFloatf2;publicDoubleg2;publicBooleanh2;@JsonField("name")publicStringi2;publicByte[]a3;publicCharacter[]b3;publicShort[]c3;publicInteger[]d3;publicLong[]e3;publicFloat[]f3;publicDouble[]g3;publicBoolean[]h3;publicString[]i3;@JsonIgnorepublicStringi4;publictransientStringi5;publicstaticStringi6;publicListk;publicListk1;publicCollectionk2;publicArrayListk3;publicSetk4;publicHashSetk5;//fastjson序列化会崩溃所例如,check@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicListk6;publicListk7;@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicList>k8;@JsonIgnorepublicList>k9;@JsonIgnorepublicMapl;publicMap>l1;publicMap>l2;publicMap,String>l3;publicMap>>l4;@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicSparseArraym1;@com.alibaba.fixedjson.annotation.JSONField(serialize=false,deserialize=false)publicSparseIntArraym2;@com.alibaba.fixedjson.annotation.JSONField(serialize=false,deserialize=false)publicSparseLongArraym3;@com.alibaba.fixedjson.annotation.JSONField(serialize=false,deserialize=false)publicSparseBooleanArraym4;publicSimpleBean2bean;@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicSimpleBean2[]bean1;@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicListbean2;@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicSetbean3;@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicListbean4;@com.alibaba.fastjson.annotation.JSONField(serialize=false,deserialize=false)publicMapbean5;}测试发现Gson的兼容性***几乎兼容所有类型,二,MSON,fastjson对嵌套类型的支持比较弱。在性能方面,MSON****、Gson和fastjson是相当的。测试结果如下:方法数MSON本身只有60个方法。使用时,会为每个标有JsonType的Bean生成2个方法,分别是:publicStringtoJson(Beanbean){...}//1publicBeanfromJson(Stringdata){...}//2另外,MSON不需要保留任何课程。如何使用MSON下面介绍如何使用MSON。过程很简单:1.注解Bean@JsonTypepublicclassBean{publicStringname;publicintage;@JsonField("_desc")publicStringdescription;//使用JsonField标记json中的keypublictransientbooleanstate;//使用transient不会被序列化@JsonIgnorepublicintstate2;//使用JsonIgnore注解不会被序列化}2.需要序列化的地方MSON.fromJson(json,clazz);//反序列化MSON.toJson(bean);//序列化结语本文介绍MSON,一种高性能的JSON序列化工具,以及它的原因和实施原则。目前我们已经在很多对性能要求比较高的地方使用了它,可以大大减少JSON的序列化时间。【本文为栏目机构“美团点评技术团队”原创稿件,转载请微信联系机构♂获得授权】点此查看作者更多好文