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

    上篇地址:http://www.iflym.com/index.php/code/alibaba-fastjson-serializer-source-analyse-2-performence-optimize-a.html
    前面讲了进行对象解析的两个方面,并讲了针对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)方法,参考其中的实现:

public ObjectSerializer getObjectWriter(Class<?> clazz) {
        ObjectSerializer writer = mapping.get(clazz);
        if (writer == null) {
            if (Map.class.isAssignableFrom(clazz)) {
                mapping.put(clazz, MapSerializer.instance);
            } else if (List.class.isAssignableFrom(clazz)) {
                mapping.put(clazz, ListSerializer.instance);
            } else if (Collection.class.isAssignableFrom(clazz)) {
                mapping.put(clazz, CollectionSerializer.instance);
            } else if (Date.class.isAssignableFrom(clazz)) {
                mapping.put(clazz, DateSerializer.instance);
            } else if (JSONAware.class.isAssignableFrom(clazz)) {
                mapping.put(clazz, JSONAwareSerializer.instance);
            } else if (JSONStreamAware.class.isAssignableFrom(clazz)) {
                mapping.put(clazz, JSONStreamAwareSerializer.instance);
            } else if (clazz.isEnum()) {
                mapping.put(clazz, EnumSerializer.instance);
            } else if (clazz.isArray()) {
                Class<?> componentType = clazz.getComponentType();
                ObjectSerializer compObjectSerializer = getObjectWriter(componentType);
                mapping.put(clazz, new ArraySerializer(compObjectSerializer));
            } else if (Throwable.class.isAssignableFrom(clazz)) {
                mapping.put(clazz, new ExceptionSerializer(clazz));
            } else {
                mapping.put(clazz, new JavaBeanSerializer(clazz));
            }
            writer = mapping.get(clazz);
        }
        return writer;
    }

    首先,取对象解析器是由一个类型为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方法即可,而不需要作额外的工作。比如对于字符串解析器,它的解析过程如下所示:

SerializeWriter out = serializer.getWrier();
        String value = (String) object;
        if (serializer.isEnabled(SerializerFeature.UseSingleQuotes)) {
            out.writeStringWithSingleQuote(value);
        } else {
            out.writeStringWithDoubleQuote(value);
        }

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

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

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

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

public final void write(JSONSerializer serializer, Object object) throws IOException {
        SerializeWriter out = serializer.getWrier();//取得输出器
        List<?> list = (List<?>) object;//强制转换为所需类型

        final int size = list.size();
        int end = size - 1;//此处定义为size-1,是因为对最后一位有特殊处理
//空集合判断,省略之
        out.append('[');//集合前缀包装
/** 以下代码使用get(X)方法访问下标,实现代码对于ArrayList实现有好处,对于LinkedList是否有好处,还待考虑 */
        for (int i = 0; i < end; ++i) {
            Object item = list.get(i);
            //空值判断
                Class<?> clazz = item.getClass();
                if (clazz == Integer.class) {//针对Integer.class特殊优化,使用outWriter自带方法
                    out.writeIntAndChar(((Integer) item).intValue(), ',');
                } else if (clazz == Long.class) {//针对Long.class特殊优化,使用outWriter自带方法
                    long val = ((Long) item).longValue();
                    out.writeLongAndChar(val, ',');
                } else {
                    serializer.write(item);//递归调用,写集合内元素
                    out.append(',');//间隔符
                }
        }

/** 以下代码为最后一位优化,当为最后一位时,不再需要输出间隔符,而是输出后缀包装符
这里即在处理时,直接输出后缀,与前面输出间隔符相对应 */
        Object item = list.get(end);
            Class<?> clazz = item.getClass();

            if (clazz == Integer.class) {
                out.writeIntAndChar(((Integer) item).intValue(), ']');
            } else if (clazz == Long.class) {
                out.writeLongAndChar(((Long) item).longValue(), ']');
            } else {
                serializer.write(item);
                out.append(']');
            }
    }

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

public void write(JSONSerializer serializer, Object object) throws IOException {
        SerializeWriter out = serializer.getWrier();
        Collection<?> collection = (Collection<?>) object;
        out.append('[');
        boolean first = true;
        for (Object item : collection) {
            if (!first) {out.append(',');}
            first = false;

            Class<?> clazz = item.getClass();
            //Integer.class和Long.class特殊处理
            serializer.write(item);
        }
        out.append(']');
    }

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

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

public void write(JSONSerializer serializer, Object object) throws IOException {
        SerializeWriter out = serializer.getWrier();
        Map<?, ?> map = (Map<?, ?>) object;
        out.write('{');//前缀

        Class<?> preClazz = null;//缓存前一个value类型和相对应的解析器,减少类型判断解析
        ObjectSerializer preWriter = null;

        boolean first = true;
        for (Map.Entry<?, ?> entry : map.entrySet()) {
//此处有删除,即根据nameFilter和valueFilter针对key-value作转换处理
            if (!first) {out.write(',');}//输出间隔符

            serializer.writeFieldName(key);//输出字段名+冒号
            first = false;

            Class<?> clazz = value.getClass();
            if (clazz == preClazz) {//此处即细节优化内容,直接使用前一个解析器,避免再次从jsonSerializer中查找
                preWriter.write(serializer, value);
            } else {
/** 此处则就需要从jsonSerializer中查找解析器,并输出了 */
                preClazz = clazz;
                preWriter = serializer.getObjectWriter(clazz);
                preWriter.write(serializer, value);
            }
        }
        out.write('}');//后缀
    }

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

public void write(JSONSerializer serializer, Object object) throws IOException {
        SerializeWriter out = serializer.getWrier();
            out.append('{');//前缀

            for (int i = 0; i < getters.length; ++i) {
                FieldSerializer getter = getters[i];//取属性解析器
                Object propertyValue = getter.getPropertyValue(object);//取值
//省略中间nameFilter和valueFilter过滤处理
                if (commaFlag) {out.append(',');}//间隔符
//省略nameFilter和valueFilter过滤之后的输出处理
               getter.writeProperty(serializer, propertyValue);//使用字段解析器输出内容
            }
            out.append('}');//后缀
    }

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

    总结

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

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/alibaba-fastjson-serializer-source-analyse-2-performence-optimize-b-html.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注