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

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

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

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

    对于第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. public ObjectSerializer getObjectWriter(Class<?> clazz) {  
  2.         ObjectSerializer writer = mapping.get(clazz);  
  3.         if (writer == null) {  
  4.             if (Map.class.isAssignableFrom(clazz)) {  
  5.                 mapping.put(clazz, MapSerializer.instance);  
  6.             } else if (List.class.isAssignableFrom(clazz)) {  
  7.                 mapping.put(clazz, ListSerializer.instance);  
  8.             } else if (Collection.class.isAssignableFrom(clazz)) {  
  9.                 mapping.put(clazz, CollectionSerializer.instance);  
  10.             } else if (Date.class.isAssignableFrom(clazz)) {  
  11.                 mapping.put(clazz, DateSerializer.instance);  
  12.             } else if (JSONAware.class.isAssignableFrom(clazz)) {  
  13.                 mapping.put(clazz, JSONAwareSerializer.instance);  
  14.             } else if (JSONStreamAware.class.isAssignableFrom(clazz)) {  
  15.                 mapping.put(clazz, JSONStreamAwareSerializer.instance);  
  16.             } else if (clazz.isEnum()) {  
  17.                 mapping.put(clazz, EnumSerializer.instance);  
  18.             } else if (clazz.isArray()) {  
  19.                 Class<?> componentType = clazz.getComponentType();  
  20.                 ObjectSerializer compObjectSerializer = getObjectWriter(componentType);  
  21.                 mapping.put(clazz, new ArraySerializer(compObjectSerializer));  
  22.             } else if (Throwable.class.isAssignableFrom(clazz)) {  
  23.                 mapping.put(clazz, new ExceptionSerializer(clazz));  
  24.             } else {  
  25.                 mapping.put(clazz, new JavaBeanSerializer(clazz));  
  26.             }  
  27.             writer = mapping.get(clazz);  
  28.         }  
  29.         return writer;  
  30.     }  

 

首先,取对象解析器是由一个类型为JSONSerializerMap的对象mapping中取。之所以使用此类型,这是性能优化的一部分,此类型并不是一个完整的map类型,只是实现了一个类似map的操作的类型。其内部并没有使用基于equals的比较方式,而是使用了System.identityHashCode的实现。对于一个对象,其identityHashCode的值是定值,而对于一个类型,在整个jvm中只有一个,因此这里使用基于class的identityHashCode是可以了,也避免了使用class的euqlas来进行比较。
    接着再根据每一个类型从mapping中取出相对应的解析器。首先从继承而来的全局解析器取得解析器,如果对象不属于第1,2,3,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. SerializeWriter out = serializer.getWrier();  
  2.         String value = (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. public final void write(JSONSerializer serializer, Object object) throws IOException {  
  2.         SerializeWriter out = serializer.getWrier();//取得输出器  
  3.         List<?> list = (List<?>) object;//强制转换为所需类型  
  4.    
  5.         final int size = list.size();  
  6.         int end = size - 1;//此处定义为size-1,是因为对最后一位有特殊处理  
  7. //空集合判断,省略之  
  8.         out.append('[');//集合前缀包装  
  9. /** 以下代码使用get(X)方法访问下标,实现代码对于ArrayList实现有好处,对于LinkedList是否有好处,还待考虑 */  
  10.         for (int i = 0; i < end; ++i) {  
  11.             Object item = list.get(i);  
  12.             //空值判断  
  13.                 Class<?> clazz = item.getClass();  
  14.                 if (clazz == Integer.class) {//针对Integer.class特殊优化,使用outWriter自带方法  
  15.                     out.writeIntAndChar(((Integer) item).intValue(), ',');  
  16.                 } else if (clazz == Long.class) {//针对Long.class特殊优化,使用outWriter自带方法  
  17.                     long val = ((Long) item).longValue();  
  18.                     out.writeLongAndChar(val, ',');  
  19.                 } else {  
  20.                     serializer.write(item);//递归调用,写集合内元素  
  21.                     out.append(',');//间隔符  
  22.                 }  
  23.         }  
  24.    
  25. /** 以下代码为最后一位优化,当为最后一位时,不再需要输出间隔符,而是输出后缀包装符 
  26. 这里即在处理时,直接输出后缀,与前面输出间隔符相对应 */  
  27.         Object item = list.get(end);  
  28.             Class<?> clazz = item.getClass();  
  29.    
  30.             if (clazz == Integer.class) {  
  31.                 out.writeIntAndChar(((Integer) item).intValue(), ']');  
  32.             } else if (clazz == Long.class) {  
  33.                 out.writeLongAndChar(((Long) item).longValue(), ']');  
  34.             } else {  
  35.                 serializer.write(item);  
  36.                 out.append(']');  
  37.             }  
  38.     }  

 

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

Java代码    收藏代码
  1. public void write(JSONSerializer serializer, Object object) throws IOException {  
  2.         SerializeWriter out = serializer.getWrier();  
  3.         Collection<?> collection = (Collection<?>) object;  
  4.         out.append('[');  
  5.         boolean first = true;  
  6.         for (Object item : collection) {  
  7.             if (!first) {out.append(',');}  
  8.             first = false;  
  9.    
  10.             Class<?> clazz = item.getClass();  
  11.             //Integer.class和Long.class特殊处理  
  12.             serializer.write(item);  
  13.         }  
  14.         out.append(']');  
  15.     }  

 

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

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

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

 

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

Java代码    收藏代码
  1. public void write(JSONSerializer serializer, Object object) throws IOException {  
  2.         SerializeWriter out = serializer.getWrier();  
  3.             out.append('{');//前缀  
  4.    
  5.             for (int i = 0; i < getters.length; ++i) {  
  6.                 FieldSerializer getter = getters[i];//取属性解析器  
  7.                 Object propertyValue = getter.getPropertyValue(object);//取值  
  8. //省略中间nameFilter和valueFilter过滤处理  
  9.                 if (commaFlag) {out.append(',');}//间隔符  
  10. //省略nameFilter和valueFilter过滤之后的输出处理  
  11.                getter.writeProperty(serializer, propertyValue);//使用字段解析器输出内容  
  12.             }  
  13.             out.append('}');//后缀  
  14.     }  

 

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

    总结

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

转载于:https://my.oschina.net/gai/blog/24124

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值