加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

alibaba fastjson(json序列化器)序列化部分源码解析-2-性能优化B

发布时间:2020-12-16 19:29:38 所属栏目:百科 来源:网络整理
导读:前面讲了进行对象解析的两个方面,并讲了针对outWriter将不同类型的数据信息写到buf字符数组。本篇讲解对象解析的过程,即如何将不同类型的对象解析成outWriter所需要的序列信息。并考虑其中的性能优化。 取得解析器 首先我们需要取得指定对象的json序列化器

前面讲了进行对象解析的两个方面,并讲了针对outWriter将不同类型的数据信息写到buf字符数组。本篇讲解对象解析的过程,即如何将不同类型的对象解析成outWriter所需要的序列信息。并考虑其中的性能优化。

取得解析器
首先我们需要取得指定对象的json序列化器,以便使用特定的序列化器来序列化对象。因此,需要有一个方法来取得相对应的序列化器。在fastjson中,使用了一个类似map的结构来保存对象类型和及对应的解析器。对于对象类型,在整个fastjson中,分为以下几类:

1基本类型以及其包装类型,字符串
2基本类型数组以及包装类型数组
3Atomic类型
4JMX类型
5集合类型以及子类
6时间类型
7json类型
8对象数组类型
9javaBean类型

对于第1,2,3,4类型,在fastjson中使用了一个全局的单态实例来保存相对应的解析器;第5类型,处理集合类型,对于集合类型及其,由于其处理逻辑均是一样,所以只需要针对子类作一些的处理,让其返回相对应的集合类型解析器即可;第6类型,时间处理器,将时间转化为类似yyyy-MM-ddTHH:mm:ss.SSS的格式;第7类型,处理fastjson专有jsonAwre类型;第8类型,处理对象的数组形式,即处理数组时,需要考虑数组中的统一对象类型;第9,即处理我们最常使用的对象,javaBean类型,这也是在项目中解析得最多的类型。

我们要看一下相对应的取解析器的方法,即类JsonSerializer.getObjectWriter(Class<?> clazz)方法,参考其中的实现:

Java代码
  1. publicObjectSerializergetObjectWriter(Class<?>clazz){
  2. ObjectSerializerwriter=mapping.get(clazz);
  3. if(writer==null){
  4. if(Map.class.isAssignableFrom(clazz)){
  5. mapping.put(clazz,MapSerializer.instance);
  6. }elseif(List.class.isAssignableFrom(clazz)){
  7. mapping.put(clazz,ListSerializer.instance);
  8. }elseif(Collection.class.isAssignableFrom(clazz)){
  9. mapping.put(clazz,CollectionSerializer.instance);
  10. }elseif(Date.class.isAssignableFrom(clazz)){
  11. mapping.put(clazz,DateSerializer.instance);
  12. }elseif(JSONAware.class.isAssignableFrom(clazz)){
  13. mapping.put(clazz,JSONAwareSerializer.instance);
  14. }elseif(JSONStreamAware.class.isAssignableFrom(clazz)){
  15. mapping.put(clazz,JSONStreamAwareSerializer.instance);
  16. }elseif(clazz.isEnum()){
  17. mapping.put(clazz,EnumSerializer.instance);
  18. }elseif(clazz.isArray()){
  19. Class<?>componentType=clazz.getComponentType();
  20. ObjectSerializercompObjectSerializer=getObjectWriter(componentType);
  21. mapping.put(clazz,newArraySerializer(compObjectSerializer));
  22. }elseif(Throwable.class.isAssignableFrom(clazz)){
  23. mapping.put(clazz,newExceptionSerializer(clazz));
  24. }else{
  25. mapping.put(clazz,newJavaBeanSerializer(clazz));
  26. }
  27. writer=mapping.get(clazz);
  28. }
  29. returnwriter;
  30. }

首先,取对象解析器是由一个类型为JSONSerializerMap的对象mapping中取。之所以使用此类型,这是性能优化的一部分,此类型并不是一个完整的map类型,只是实现了一个类似map的操作的类型。其内部并没有使用基于equals的比较方式,而是使用了System.identityHashCode的实现。对于一个对象,其identityHashCode的值是定值,而对于一个类型,在整个jvm中只有一个,因此这里使用基于class的identityHashCode是可以了,也避免了使用class的euqlas来进行比较。
接着再根据每一个类型从mapping中取出相对应的解析器。首先从继承而来的全局解析器取得解析器,如果对象不属于第1,4类型,而开始进入以下的if else阶段。
我们从上面的源码中,可以看出解析器主要分为两个部分,一个是与解析类型相关的,一个是无关的。比如对于第5,6,7类型,其中最5类型是集合类型,由于不知道集合类型中的具体类型(因为存在继承关系),所以类型无关。对于第8,9类型,其中第8类型为对象数组类型,对于对象数组,数组中的每一个对象的类型都是确定的,且整个数组只有一种类型,因此可以确定其类型,这时候就要使用类型相关解析器了,对于第9类型,需要使用解析对象的类型来确定相对应的javaBean属性,因此是类型相关。
另外一个确定解析器的过程当中,使用了映射机制,即将当前解析器与对应的类型映射起来,以便下一次时使用。对于集合类型及子类型,由于当前类型并不是确定的List或Collection类型,因此将当前类型与集合解析器映射起来。对于对象类型,需要将当前类型传递给相对应的解析器,以确定具体的属性。

解析过程

解析方法由统一的接口所定义,每个不同的解析器只需要实际这个方法并提供各自的实现即可。此方法为write(JSONSerializer serializer,Object object),由ObjectSerializer提供。带两个参数,第一个参数,即为解析的起点类jsonSerializer,此类封装了我们所需要的outWriter类,需要时只需要从此类取出即可。第二个参数为我们所要解析的对象,在各个子类进行实现时,可将此对象通过强制类型转换,转换为所需要的类型即可。
具体的解析过程根据不同的数据类型不所不同,对于第1,2类型,由于在outWriter中均有相对应的方法,所以在具体实现时,只需要调用相应的outWriter方法即可,而不需要作额外的工作。比如对于字符串解析器,它的解析过程如下所示:

Java代码
  1. SerializeWriterout=serializer.getWrier();
  2. Stringvalue=(String)object;
  3. if(serializer.isEnabled(SerializerFeature.UseSingleQuotes)){
  4. out.writeStringWithSingleQuote(value);
  5. }else{
  6. out.writeStringWithDoubleQuote(value);
  7. }

即首先,取得输出时所需要的outWriter,然后再根据配置决定是输出单引号+字符串还是双引号+字符串。

而对于其它并没有由outWriter所直接支持的类型而言,解析过程就相对于复杂一些。但是总的逻辑还是基于以下逻辑:

  1. 基于数据类型特点输出所特有的字符包装内容
  2. 基于数据类型特点转换为outWriter所能识别的内容
  3. 逐步解析,将对象解析产生的字符数组输出到outWriter中

只需此三步,即可完美的解析一个对象。因此,我们可以参考其中的一个具体实现,并讨论其中的具体优化措施。
在取得解析器方法getObjectWriter(Class<?> clazz)中,我们可以看到,对于集合类型中的Collection和List,fastjson是分开进行解析的。即两者在解析时在细微处有着不一样的实现。两者的区别在于List是有序的,可以根据下标对元素进行访问,对于常用List实现,ArrayList,使用下标访问子元素的价格为O1。这就是在fastJson中采取的一点优化措施,详细看以下实现代码:

Java代码
  1. publicfinalvoidwrite(JSONSerializerserializer,Objectobject)throwsIOException{
  2. SerializeWriterout=serializer.getWrier();//取得输出器
  3. List<?>list=(List<?>)object;//强制转换为所需类型
  4. finalintsize=list.size();
  5. intend=size-1;//此处定义为size-1,是因为对最后一位有特殊处理
  6. //空集合判断,省略之
  7. out.append('[');//集合前缀包装
  8. /**以下代码使用get(X)方法访问下标,实现代码对于ArrayList实现有好处,对于LinkedList是否有好处,还待考虑*/
  9. for(inti=0;i<end;++i){
  10. Objectitem=list.get(i);
  11. //空值判断
  12. Class<?>clazz=item.getClass();
  13. if(clazz==Integer.class){//针对Integer.class特殊优化,使用outWriter自带方法
  14. out.writeIntAndChar(((Integer)item).intValue(),',');
  15. }elseif(clazz==Long.class){//针对Long.class特殊优化,使用outWriter自带方法
  16. longval=((Long)item).longValue();
  17. out.writeLongAndChar(val,');
  18. }else{
  19. serializer.write(item);//递归调用,写集合内元素
  20. out.append(',');//间隔符
  21. }
  22. }
  23. /**以下代码为最后一位优化,当为最后一位时,不再需要输出间隔符,而是输出后缀包装符
  24. 这里即在处理时,直接输出后缀,与前面输出间隔符相对应*/
  25. Objectitem=list.get(end);
  26. Class<?>clazz=item.getClass();
  27. if(clazz==Integer.class){
  28. out.writeIntAndChar(((Integer)item).intValue(),']');
  29. }elseif(clazz==Long.class){
  30. out.writeLongAndChar(((Long)item).longValue(),']');
  31. }else{
  32. serializer.write(item);
  33. out.append(']');
  34. }
  35. }

以下实现与collection相比不同的即在于处理中间元素与末尾元素的区别。相对于Collection,就不能使用以上的方法了,在实现上,就只能先输出前缀,再一个一个地处理里面的元素,最后输出后缀。此实现如下所示:

Java代码
  1. publicvoidwrite(JSONSerializerserializer,Objectobject)throwsIOException{
  2. SerializeWriterout=serializer.getWrier();
  3. Collection<?>collection=(Collection<?>)object;
  4. out.append('[');
  5. booleanfirst=true;
  6. for(Objectitem:collection){
  7. if(!first){out.append(',');}
  8. first=false;
  9. Class<?>clazz=item.getClass();
  10. //Integer.class和Long.class特殊处理
  11. serializer.write(item);
  12. }
  13. out.append(']');
  14. }

以上代码就是通常最常见的实现了。

相对于集合类型实现,map实现和javaBean实现相对来说,稍微复杂了一点。主要是输出key和value的问题。在fastjson中,key输出表现为使用outWriter的writeKey来进行输出,value输出则同样使用常规的输出。按照我们先前所说的逻辑,首先还是输出包装字符内容,即{,在末尾输出}。然后,再根据每个key-value映射特点,采取相对应的输出方式。
当然,对于map类型输出和javaBean输出还是不一样的。两者可以互相转换,但fastjson在输出时还是采取了不一样的输出方式。那么,我们以源代码来查看相应的实现:

Java代码
  1. publicvoidwrite(JSONSerializerserializer,Objectobject)throwsIOException{
  2. SerializeWriterout=serializer.getWrier();
  3. Map<?,?>map=(Map<?,?>)object;
  4. out.write('{');//前缀
  5. Class<?>preClazz=null;//缓存前一个value类型和相对应的解析器,减少类型判断解析
  6. ObjectSerializerpreWriter=null;
  7. booleanfirst=true;
  8. for(Map.Entry<?,?>entry:map.entrySet()){
  9. //此处有删除,即根据nameFilter和valueFilter针对key-value作转换处理
  10. if(!first){out.write(',');}//输出间隔符
  11. serializer.writeFieldName(key);//输出字段名+冒号
  12. first=false;
  13. Class<?>clazz=value.getClass();
  14. if(clazz==preClazz){//此处即细节优化内容,直接使用前一个解析器,避免再次从jsonSerializer中查找
  15. preWriter.write(serializer,value);
  16. }else{
  17. /**此处则就需要从jsonSerializer中查找解析器,并输出了*/
  18. preClazz=clazz;
  19. preWriter=serializer.getObjectWriter(clazz);
  20. preWriter.write(serializer,value);
  21. }
  22. }
  23. out.write('}');//后缀
  24. }

由上可以看出,map的实现还是按照我们常用的思路在进行。在性能优化处,采取了两个优化点,一是输出字段时,并不直接输出字段名,而是采取字段+冒号的方式一起输出,减少outWriter扩容计算。二是在查找value解析器时,尽量使用前一个value的解析器,避免重复查找。
相比map,javaBean的实现就相对更复杂。javaBean输出并不是采取key-value的方式,而是采取类似fieldSerializer的输出方式,即将属性名和值,组合成一个字段,一起进行输出。当然输出时还是先输出字段名,再输出值的实现。那么对于javaBean实现,首先要取得当前对象类型的所有可以输出的类型。
在fastjson实现中,并没有采取javaBean属性的读取方式,而是采取了使用getXXX和isXXX方法的读取模式,来取得一个类型的可读取属性。作为性能优化的一部分,读取属性的操作结果被缓存到了getter缓存器中。其实,并不是缓存到了getter缓存器中,只是该类型的javaBean序列化器对象被缓存到了jsonSerializer的对象类型-序列化器映射中。对于同一个类型,就不需要再次解析该类型的属性了。
有了相对应的字段,那么在实现时,就按照相应的字段进行输出即可。以下为实现代码:

Java代码
  1. publicvoidwrite(JSONSerializerserializer,Objectobject)throwsIOException{
  2. SerializeWriterout=serializer.getWrier();
  3. out.append('{');//前缀
  4. for(inti=0;i<getters.length;++i){
  5. FieldSerializergetter=getters[i];//取属性解析器
  6. ObjectpropertyValue=getter.getPropertyValue(object);//取值
  7. //省略中间nameFilter和valueFilter过滤处理
  8. if(commaFlag){out.append(',');}//间隔符
  9. //省略nameFilter和valueFilter过滤之后的输出处理
  10. getter.writeProperty(serializer,propertyValue);//使用字段解析器输出内容
  11. }
  12. out.append('}');//后缀
  13. }

由上可见,javaBean的输出实际上和map输出差不多。只不过这里又把属性的解析和输出封装了一层。在使用字段解析器(由FieldSerializer标识)输出字段值时,实际上也是先输出字段名+冒号,再输出字段值。这里就不再详细叙述。

总结

在整个解析过程中,更多的是根据对象类型查找到对象解析器,再使用对象解析器序列化对象的过程。在这中间,根据不同的对象采取不同的解析,并在实现中采取部分优化措施,以尽量地提高解析效率,减少中间运算。减少中间运算,是在解析过程中采取的最主要的优化办法。实际上,最主要的优化措施还是体现在outWriter中对于数据的处理上。此处更多的是设计模式的使用,以实现繁多的对象的解析。 整个fastjson的序列化部分,就到此为止。单就笔者而言,在查看源代码的时候,也发现了一些问题,可能是作者未考虑的问题,或者是实际中未遇到。但在版本升级过程中,也渐渐地对功能进行了增强,比如对于@JsonField注解的使用,NameFilter和ValueFilter的使用,使fastjson越来越符合业务系统的需要。如果可以,笔者会将其用到笔者所在的项目中,而不再重复发明轮子:)

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读