GVKun编程网logo

FastJson 序列化java bean中的属性顺序问题(alibaba.fastjson)(fastjson序列化指定字段)

8

本文将介绍FastJson序列化javabean中的属性顺序问题(alibaba.fastjson)的详细情况,特别是关于fastjson序列化指定字段的相关信息。我们将通过案例分析、数据研究等多种方

本文将介绍FastJson 序列化java bean中的属性顺序问题(alibaba.fastjson)的详细情况,特别是关于fastjson序列化指定字段的相关信息。我们将通过案例分析、数据研究等多种方式,帮助您更全面地了解这个主题,同时也将涉及一些关于alibaba fastjson (json 序列化器) 序列化部分源码解析 - 2 - 性能优化 A、alibaba fastjson (json 序列化器) 序列化部分源码解析 - 2 - 性能优化 B、Alibaba fastjson 序列化与反序列化、alibaba fastjson(json序列化器)序列化部分源码解析- Java综合的知识。

本文目录一览:

FastJson 序列化java bean中的属性顺序问题(alibaba.fastjson)(fastjson序列化指定字段)

FastJson 序列化java bean中的属性顺序问题(alibaba.fastjson)(fastjson序列化指定字段)

FastJson 序列化java bean中的属性顺序问题(alibaba.fastjson)

fastjson序列化一个java bean,默认是根据fieldName的字母序进行序列化的,你可以通过ordinal指定字段的顺序,这个特性需要1.1.42以上版本。示例如下。

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.annotation.JSONField;

 

public class FieldSequence {

 

    @JSONField(ordinal=1,name = "name_1")

    private String name;

    @JSONField(ordinal=2)

    private int age;

    @JSONField(ordinal=3)

    private String gender;

 

    public FieldSequence(String name,int age,String gender){

 

        this.name = name;

        this.age = age;

        this.gender = gender;

    }

 

    public String getName() {

        return name;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public int getAge() {

        return age;

    }

 

    public void setAge(int age) {

        this.age = age;

    }

 

    public String getGender() {

        return gender;

    }

 

    public void setGender(String gender) {

        this.gender = gender;

    }

 

    public static void main(String[] args){

        System.out.println(JSON.toJSONString(fieldSequence));

    }

 

}

属性中不使用@JSONField注解输出如下

{"age":20,"gender":"woman","name":"lily"}  //按属性字母顺序排序

·         1

使用注解后,上面程序输出如下

{"name_1":"lily","age":20,"gender":"woman"}

一种方式 @JSONType(orders={“”,””,””,””})字段名的顺序

另一种 @JSONField注解中可以设置属性顺序,重新设置属性名称,格式等,SerializerFeature(这是个枚举类,里面封装的有很多序列化的格式需求)

 

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

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

接上篇,在论述完基本概念和总体思路之后,我们来到整个程序最重要的部分 - 性能优化。之所以会有 fastjson 这个项目,主要问题是为了解决性能这一块的问题,将序列化工作提高到一个新的高度。我们提到,性能优化主要有两个方面,一个如何将处理后的数据追加到数据储存器,即 outWriter 中;二是如何保证处理过程中的速度。
    本篇从第一个性能优化方面来进行解析,主要的工作集中在类 SerializeWriter 上。

    首先,类的声明,继承了 Writer 类,实现了输出字符的基本功能,并且提供了拼接数据的基本功能。内部使用了一个 buf 数组和 count 来进行计数。这个类的实现结果和 StringBuilder 的工作模式差不多。但我们说为什么不使用 StringBuilder,主要是因为 StringBuilder 没有针对 json 序列化提出更加有效率的处理方式,而且单就 StringBuilder 而言,内部是为了实现字符串拼接而生,因为很自然地使用了更加能够读懂的方式进行处理。相比,serializeWriter 单处理 json 序列化数据传输,功能单一,因此在某些方面更加优化一些。
    在类声明中,这里有一个优化措施 (笔者最开始未注意到,经作者指出之后才明白)。即是对 buf 数组的缓存使用,即在一次处理完毕之后,储存的数据容器并不销毁,而是留在当前线程变量中。以便于在当前线程中再次序列化 json 时使用。源码如下:

Java 代码    收藏代码
  1. public SerializeWriter(){  
  2.         buf = bufLocal.get(); // new char[1024];  
  3.         if (buf == null) {  
  4.             buf = new char[1024];  
  5.         } else {  
  6.             bufLocal.set(null);  
  7.         }  
  8.     }  

 

 在初始构造时,会从当前线程变量中取 buf 数组并设置在对象属性 buf 中。而在每次序列化完成之后,会通过 close 方法,将此 buf 数组再次绑定在线程变量当中,如下所示:

Java 代码    收藏代码
  1. /** 
  2.      * Close the stream. This method does not release the buffer, since its contents might still be required. Note: 
  3.      * Invoking this method in this class will have no effect. 
  4.      */  
  5.     public void close() {  
  6.         bufLocal.set(buf);  
  7.     }  

 

当然,buf 重新绑定了,肯定计数器 count 应该置 0。这是自然,count 是对象属性,每次在新建时,自然会置 0。

    在实现过程当中,很多具体的实现是借鉴了 StringBuilder 的处理模式的,在以下的分析中会说到。

    总体分类
   
    接上篇而言,我们说 outWriter 主要实现了五个方面的输出内容。
        1, 提供 writer 的基本功能,输出字符,输出字符串
        2, 提供对整形和长整形输出的特殊处理
        3, 提供对基本类型数组输出的支持
        4, 提供对整形 + 字符的输出支持
        5, 提供对字符串 + 双(单)引号的输出方式
    五个方面主要体现在不同的作用域。第一个提供了最基本的 writer 功能,以及在输出字符上最基本的功能,即拼接字符数组(不是字符串);第二个针对最常用的数字进行处理;第三个,针对基本类型数组类处理;第四个针对在处理集合 / 数组时,最后一位的特殊处理,联合了输出数字和字符的双重功能,效率上比两个功能的实现原理上更快一些;第四个,针对字符串的特殊处理 (主要是特殊字符处理) 以及在 json 中,字符串的引号处理(即在 json 中,字符串必须以引号引起来)。

    实现思想

    数据输出最后都变成了拼接字符的功能,即将各种类型的数据转化为字符数组的形式,然后将字符数组拼接到 buf 数组当中。这中间主要逻辑如下:
        1    对象转化为字符数组
        2    准备装载空间,以容纳数据
        2.1    计数器增加
        2.2    扩容,字符数组扩容
        3    装载数据
        4    计数器计数最新的容量,完成处理
    这里面主要涉及到一个 buf 数组扩容的概念,其使用的扩容函数 expandCapacity 其内部实现和 StringBuilder 中一样。即(当前容量 + 1)* 2,具体可以见相应函数或 StringBuilder.ensureCapacityImpl 函数。

 

    实现解析

    基本功能
    基本功能有以下几个函数:

Java 代码    收藏代码
  1. public void write(int c)  
  2. public void write(char c)  
  3. public void write(char c[], int off, int len)  
  4. public void write(String str, int off, int len)  
  5. public SerializeWriter append(CharSequence csq)  
  6. public SerializeWriter append(CharSequence csq, int start, int end)  
  7. public SerializeWriter append(char c)  

 

     其中第一个函数,可以忽略,可以理解为实现 writer 中的 writ (int) 方法,在具体应用时未用到此方法。第 2 个方法和第 7 个方法为写单个字符,即往 buf 数组中写字符。第 3,4,5,6, 均是写一个字符数组(字符串也可以理解为字符数组)。因此,我们单就字符数组进行分析,源码如下:

Java 代码    收藏代码
  1. public void write(char c[], int off, int len) {  
  2.         int newcount = count + len;// 计算新计数量  
  3.         // 扩容计算  
  4.         System.arraycopy(c, off, buf, count, len);// 拼接字符数组  
  5.         count = newcount;// 最终计数  
  6.     }  

 

从上注释可以看出,其处理流程和我们所说的标准处理逻辑一致。在处理字符拼接时,尽量使用最快的方法,如使用 System.arrayCopy 和字符串中的 getChars 方法。另外几个方法处理逻辑与此方法相同。
    警告:不要在正式应用中对有存在特殊字符的字符串 (无特殊字符的字符串除外) 使用以上的输出方式,请使用第 5 组方式进行 json 输出。对于字符数组的处理在以上处理方式中不会对特殊字符进行处理。如字符串 3\"''4, 在使用以上方式输出时,只会输出 3"''4, 其中的转义字符在转化为 toChar 时被删除掉。
    因此,在实际处理中,只有字符数组会使用以上方式进行输出。不要将字符串与字符数组相混合。字符数组不考虑转义问题,而字符串需要考虑转义。

    整形和长整形

    方法如下:

Java 代码    收藏代码
  1. public void writeInt(int i)  
  2. public void writeLong(long i)  

 

    这两个方法,按照我们的逻辑,首先需要将整性和长整性转化为字符串(无特殊字符),然后以字符数组的形式输出即可。在进行处理时,主要参考了 Integer 和 Long 的 toString 实现方式和长度计算。首先看一个实现:

Java 代码    收藏代码
  1. public void writeInt(int i) throws IOException {  
  2.         if (i == Integer.MIN_VALUE) {// 特殊数字处理  
  3.             write("-2147483648");  
  4.             return;  
  5.         }  
  6.    
  7.         int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);// 计算长度 A  
  8.         int newcount = count + size;  
  9.   // 扩容计算  
  10.         IOUtils.getChars(i, newcount, buf);// 写入 buf 数组 B  
  11.         count = newcount;// 最终定 count 值  
  12.     }  

 

以上首先看特殊数字的处理,因为 int 的范围从 - 2147483648 到 2147483647,因此对于 - 2147483648 这个特殊数字(不能转化为 - 号 + 正数的形式),进行特殊处理。这里调用了 write (str) 方法,实际上就是调用了在第一部分的 public void write (String str, int off, int len), 这里是安全的,因为没有特殊字符。
    其次是计算长度,两者都借鉴了 jdk 中的实现,分别为 Integer.stringSize 和 Long.stringSize,这里就不再叙述。
    再写入 buf 数组,我们说都是将数字转化为字符数组,再定入 buf 数组中。这里的实现,即按照这个步骤在进行。这里在 IOUtils 中,借鉴了 Integer.getChars (int i, int index, char [] buf) 方法和 Long.getChars (long i, int index, char [] buf) 方法,这里也不再叙述。

    基本类型数组

Java 代码    收藏代码
  1. public void writeBooleanArray(boolean[] array)  
  2. public void writeShortArray(short[] array)  
  3. public void writeByteArray(byte[] array)  
  4. public void writeIntArray(int[] array)  
  5. public void writeIntArray(Integer[] array)  
  6. public void writeLongArray(long[] array)  

 

     数组的形式,主要是将数组的每一部分输出出来,即可。在输出时,需要输出前缀 “[” 和后缀 “]” 以及每个数据之间的 “,“。按照我们的逻辑,首先还是计算长度,其次是准备空间,再者是写数据,最后是定 count 值。因此,我们参考一个实现:

Java 代码    收藏代码
  1. public void writeIntArray(int[] array) throws IOException {  
  2.         int[] sizeArray = new int[array.length];// 性能优化,用于保存每一位数字长度  
  3.         int totalSize = 2;// 初始长度,即 []  
  4.         for (int i = 0; i < array.length; ++i) {  
  5.             if (i != 0) {totalSize++;}// 追加,长度  
  6.             int val = array[i];  
  7. // 针对每一个数字取长度,此处有部分删除。分别针对 minValue 和普通 value 运算  
  8.             int size = (val < 0) ? IOUtils.stringSize(-val) + 1 : IOUtils.stringSize(val);  
  9.             sizeArray[i] = size;  
  10.             totalSize += size;  
  11.         }  
  12. // 扩容计算  
  13.         buf[count] = ''['';// 追加起即数组字符  
  14.    
  15.         int currentSize = count + 1;// 记录当前位置,以在处理数字时,调用 Int 的 getChars 方法  
  16.         for (int i = 0; i < array.length; ++i) {  
  17.             if (i != 0) {buf[currentSize++] = '','';} // 追加数字分隔符  
  18.    
  19. // 追加当前数字的字符形式,分别针对 minValue 和普通数字作处理  
  20.             int val = array[i];  
  21.                 currentSize += sizeArray[i];  
  22.                 IOUtils.getChars(val, currentSize, buf);  
  23.         }  
  24.         buf[currentSize] = '']'';// 追加结尾数组字符  
  25.         count = newcount;// 最终 count 定值  
  26.     }  

 

    此处有关于性能优化的地方,主要有几个地方。首先将 minValue 和普通数字分开计算,以避免可能出现的问题;在计算长度时,尽量调用前面使用 stringToSize 方法,此方法最快;在进行字符追加时,利用 getChars 方法进行处理。
    对于仍有优化的地方,比如对于 boolArray,在处理时,又有了特殊优化,主要还是在上面的两点,计算长度时,尽量地快,以及在字符追加时也尽量的快。以下为对于 boolean 数据的两个优化点:

Java 代码    收藏代码
  1. // 计算长度,直接取值,不需要进行计算  
  2. if (val) {  
  3.           size = 4// "true".length();  
  4.          } else {}  
  5. // 追加字符时,不需要调用默认的字符拼接,直接手动拼接,减少中间计算量  
  6. boolean val = array[i];  
  7.             if (val) {  
  8.                 // System.arraycopy("true".toCharArray(), 0, buf, currentSize, 4);  
  9.                 buf[currentSize++] = ''t'';  
  10.                 buf[currentSize++] = ''r'';  
  11.                 buf[currentSize++] = ''u'';  
  12.                 buf[currentSize++] = ''e'';  
  13.             } else {/** 省略 **/}  

 

数字 + 字符输出

Java 代码    收藏代码
  1. public void writeIntAndChar(int i, char c)  
  2. public void writeLongAndChar(long i, char c)  

 

    以上两个方法主要在处理以下情况下使用,在不知道要进行序列化的对象的长度的情况下,要尽量避免进行 buf 数据扩容的情况出现。尽管这种情况很少发生,但还是尽量避免。特殊是在输出集合数据的情况下,在集合数据输出下,各个数据的长度未定,因此不能计算出总输出长度,只能一个对象一个对象输出,在这种情况下,先要输出一个对象,然后再输出对象的间隔符或结尾符。如果先调用输出数据,再调用输出间隔符或结尾符,远不如将两者结合起来,一起进行计算和输出。
    此方法基于以下一个事实:尽量在已知数据长度的情况下进行字符拼接,这样有利于快速的为数据准备数据空间。
    在具体实现时,此方法只是减少了数据扩容的计算,其它方法与基本实现和组合是一致的,以 writeIntAndChar 为例:

Java 代码    收藏代码
  1. public void writeIntAndChar(int i, char c) throws IOException {  
  2.         //minValue 处理  
  3. // 长度计算,长度为数字长度 + 字符长度  
  4.         int size = (i < 0) ? IOUtils.stringSize(-i) + 1 : IOUtils.stringSize(i);  
  5.         int newcount0 = count + size;  
  6.         int newcount1 = newcount0 + 1;  
  7. // 扩容计算  
  8.         IOUtils.getChars(i, newcount0, buf);// 输出数字  
  9.         buf[newcount0] = c;// 输出字符  
  10.         count = newcount1;// 最终 count 定值  
  11.     }  

 

字符串处理

    作为在业务系统中最常用的类型,字符串是一个必不可少的元素之一。在 json 中,字符串是以双(单)引号,引起来使用的。因此在输出时,即要在最终的数据上追加双 (单) 引号。否则,js 会将其作为变量使用而报错。而且在最新的 json 标准中,对于 json 中的 key,也要求必须追加双 (单) 引号以示区分了。字符串处理方法有以下几种:

Java 代码    收藏代码
  1. public void writeStringWithDoubleQuote(String text)  
  2. public void writeStringWithSingleQuote(String text)  
  3. public void writeKeyWithDoubleQuote(String text)  
  4. public void writeKeyWithSingleQuote(String text)  
  5. public void writeStringArray(String[] array)  
  6. public void writeKeyWithDoubleQuoteIfHashSpecial(String text)  
  7. public void writeKeyWithSingleQuoteIfHashSpecial(String text)  

 

     其中第 1,2 方法表示分别用双引号和单引号将字符串包装起来,第 3,4 方法表示在字符串输出完毕之后,再输出一个冒号,第 5 方法表示输出一个字符串数组,使用双引号包装字符串。第 7,8 方法未知(不明真相的方法?)
    字符串是可以知道长度的,所以第一步确定长度即 OK 了。 在第一步扩容计算之后,需要处理一个在字符串中特殊的问题,即转义字符处理。如何处理转义字符,以及避免不必要的扩容计算,是必须要考虑的。在 fastjson 中,采取了首先将其认定为全非特殊字符,然后再一个个字符判断,对特殊字符再作处理的方法。在一定程序上避免了在一个个判断时,扩容计算的问题。我们就其中一个示例进行分析:

Java 代码    收藏代码
  1. public void writeStringWithDoubleQuote(String text) {  
  2. //null 处理,直接追加 null 字符即可,不需要双引号  
  3.         int len = text.length();  
  4.         int newcount = count + len + 2;// 初始计算长度为字符串长度 + 2(即双引号)  
  5. // 初步扩容计算  
  6.    
  7.         int start = count + 1;  
  8.         int end = start + len;  
  9.         buf[count] = ''\"'';// 追加起始双引号  
  10.         text.getChars(0, len, buf, start);  
  11.         count = newcount;// 初步定 count 值  
  12. /** 以下代码为处理特殊字符 */  
  13.         for (int i = start; i < end; ++i) {  
  14.             char ch = buf[i];  
  15.             if (ch == ''\b'' || ch == ''\n'' || ch == ''\r'' || ch == ''\f'' || ch == ''\\'' || ch == ''/'' || ch == ''"'') {// 判断是否为特殊字符  
  16. // 这里需要修改 count 值,以及扩容判断,省略之  
  17.                 System.arraycopy(buf, i + 1, buf, i + 2, end - i - 1);// 数据移位,从当前处理点往后移  
  18.                 buf[i] = ''\\'';// 追加特殊字符标记  
  19.                 buf[++i] = replaceChars[(int) ch];// 追加原始的特殊字符为 \b 写为 b,最终即为 \\b 的形式,而不是 \\\b  
  20.                 end++;  
  21.             }  
  22.         }  
  23.    
  24.         buf[newcount - 1] = ''\"'';// 转出结尾双引号  
  25.     }  

 

    在处理字符串上,特殊的即在特殊字符上。因为在输出时,要输出时要保存字符串的原始模式,如 \"的格式,要输出时,要输出为 \ +" 的形式,而不能直接输出为 \",后者在输出时就直接输出为",而省略了 \,这在 js 端是会报错的。

    总结:

    在针对输出优化时,主要利用了最有效率的手段进行处理。如针对数字和 boolean 时的处理方式。同时,在处理字符串时,也采取了先处理最常用字符,再处理特殊字符的形式。在针对某些经常碰到的场景时,使用了联合处理的手段(如 writeIntAndChar),而不再是分开处理。
    整个处理的思想,即是在处理单个数据时,采取最优方式;在处理复合数据时,避免扩容计算;尽量使用 jdk 中的方法,以避免重复轮子(可能轮子更慢)。

    下一篇,从数据处理过程对源码进行分析,同时解析其中针对性能优化的处理部分。

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

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 越来越符合业务系统的需要。如果可以,笔者会将其用到笔者所在的项目中,而不再重复发明轮子:)

Alibaba fastjson 序列化与反序列化

Alibaba fastjson 序列化与反序列化

fastjson在官网的定义号称最小最快 多态的json序列化工具。fastjson采用独创的算法,将parse的速度提升到极致,超过所有json库,包括曾经号称最快的jackson。并且还超越了google的二进制协议protocol buf。

JSON这个类是fastjson API的入口,主要的功能都通过这个类提供。下面是这个类的重要几个方法介绍:


序列化API

package com.alibaba.fastjson;

public abstract class JSON {
    // 将Java对象序列化为JSON字符串,支持各种各种Java基本类型和JavaBean
    public static String toJSONString(Object object,SerializerFeature... features);


    // 将Java对象序列化为JSON字符串,返回JSON字符串的utf-8 bytes
    public static byte[] toJSONBytes(Object object,SerializerFeature... features);


    // 将Java对象序列化为JSON字符串,写入到Writer中
    public static void writeJSONString(Writer writer,Object object,SerializerFeature... features);


    // 将Java对象序列化为JSON字符串,按UTF-8编码写入到OutputStream中
    public static final int writeJSONString(OutputStream os,// 
                                            Object object,// 
                                            SerializerFeature... features);
}



JSON字符串反序列化API


package com.alibaba.fastjson;


public abstract class JSON {
    // 将JSON字符串反序列化为JavaBean
    public static <T> T parSEObject(String jsonStr,Class<T> clazz,Feature... features);


    // 将JSON字符串反序列化为JavaBean
    public static <T> T parSEObject(byte[] jsonBytes,// UTF-8格式的JSON字符串
                                    Class<T> clazz,Feature... features);


    // 将JSON字符串反序列化为泛型类型的JavaBean
    public static <T> T parSEObject(String text,TypeReference<T> type,Feature... features);


    // 将JSON字符串反序列为JSONObject
    public static JSONObject parSEObject(String text);
}



Demo


parse Tree

import com.alibaba.fastjson.*;

JSONObject jsonObj = JSON.parSEObject(jsonStr);


parse POJO


import com.alibaba.fastjson.JSON;


Model model = JSON.parSEObject(jsonStr,Model.class);




parse POJO Generic


import com.alibaba.fastjson.JSON;


Type type = new TypeReference<List<Model>>() {}.getType(); 
List<Model> list = JSON.parSEObject(jsonStr,type);




convert POJO to json string

import com.alibaba.fastjson.JSON;


Model model = ...; 
String jsonStr = JSON.toJSONString(model);




convert POJO to json bytes


import com.alibaba.fastjson.JSON;


Model model = ...; 
byte[] jsonBytes = JSON.toJSONBytes(model);




write POJO as json string to OutputStream


import com.alibaba.fastjson.JSON;


Model model = ...; 
OutputStream os;
JSON.writeJSONString(os,model);




write POJO as json string to Writer


import com.alibaba.fastjson.JSON;


Model model = ...; 
Writer writer = ...;
JSON.writeJSONString(writer,model);


总结:


更多关于fastjson的知识可以去w3c去了解:https://www.w3cschool.cn/fastjson/

alibaba fastjson(json序列化器)序列化部分源码解析- Java综合

alibaba fastjson(json序列化器)序列化部分源码解析- Java综合

本文copy自http://www.flydmeng.com/index.php/code/alibaba-fastjson-json-serializer-chapter-source-analyse-one-global-analyse.html

fastjson官方地址: http://code.alibabatech.com/wiki/display/FastJSON/Home
从javaeye上看到了阿里一位人士写的fastjson,特别是其中如何将java对象序列化成json字符串这段。笔者比较关注,因为在笔者的项目中就用了一个json序列化器(造的轮子)。就下载下来看了一看,先不说和笔者所用的轮子有何区别,单就用了一个简单的测试器,来测试一下两者的处理速度。测试代码就不贴了,简单地说下测试结果。在jvm充分优化的情况下(for循环执行了很多次之后),笔者所使用的java序列化器处理速度不是很均匀,在结尾有短暂的变化(可能与虚拟机回收有关系);而fastjson在后面的处理过程当中,一般很均匀(后来发现与使用的buf分配方式有关)。最主要的区别莫在于,fastjson的速度那是不能对比了。
经过分析源码之后,发现fastjson在处理json优化上面还是下了很大的工夫的。笔者准备从以下几个方面对fastjson作一个简单的解析,也让使用fastjson的同学对fastjson有一个简单的认识。
1 总体分析 分析json序列化的总体思路和解析过程
2 性能分析A 针对字符生产部分(即outWriter)对不同类型数据的处理和与性能相关处理部分
3 性别分析B 针对序列化过程部分(即objectSerializer)对不同类型的序列化过程处理和与性能相关处理部分
4 对象解析分析 对javaBean解析部分和针对字段输出部分的处理和解析
源码分析基于1.0.5版本。

总体分析,首先上图,即fastjson的总体处理思想,其实也是所有json序列化器需要考虑的问题。

在这里,需要考虑的主要有两个部分,一是临时保存在序列化过程中用于储存数据的容器,二是处理对象序列化的序列化器。
在fastjson中,保存数据的容器使用了wirter,字符输出流,而且是自实现的一个字符输出流。相对原来的writer,追加了很多需要输出的信息的实现,比如输出一个字符串,输出一个字符,输出一个long类型数据等。而处理对象序列化的序列化器,而使用了责任链模式和工厂模式,将不同类型的java对象分散到不同的序列化器当中。而每个序列化器只处理与自身类型相对应的数据信息,这样就避免了在处理时,各种情况交织在一块,逻辑混乱的问题。
下面就源码本身作一个分析,其中结合两个部分进行分析。

代码分析部分

首先,将一个对象序列化json字符串调用的是JSON对象的toJSONString方法,这里调用的是无参数方法。(注:本文不分析采用vistor实现json序列化代码的部分)。具体代码如下所示:

第一行,新产生的一个数据保存器,储存在序列化过程中产生的数据;第二行,产生统一的json序列化器,其中使用了outWriter,此类即json序列化的统一处理器;第三行,调用序列化方法开始序列化对象,以产生json字符串信息;第四行,返回已经储存的json信息。

Java代码
  1. SerializeWriterout=newSerializeWriter();
  2. JSONSerializerserializer=newJSONSerializer(out);
  3. serializer.write(object);
  4. returnout.toString();

数据保存器(序列化输出容器)

SerializeWriter是一个用于储存在序列化过程中产生的数据信息,它与jdk中的StringBuiler有着类似的功能,即将不同的数据填充到此容器中。之所以不使用StringBuilder的原因之一在于StringBuilder没有提供一些特别为性能优化的方法,并且StringBuilder在处理过程中增加了多余的操作(如新分配对象)。该容器的主要功能就是接收不同的数据,并将这些数据存储到该内部的一个字符数组当中,同时记录字符总数。
既然充当了一个数据输出的角色,那么就可以往其中输入任何的数据,包括int,byte,short等基本类型和对应的包装类型,也包括日期数据,以及经常使用的字符串数据等。对于在这些数据类型之外的其它类型,由于json的特殊结构,所有的高级类型均可以转化于这些基础类型的组织体,所以不需要再针对高级类型作处理了(这些是序列化器应该考虑的问题)。

首先对SerializeWriter的方法(输出和追加)作一个预览:

方法总共可以分五个部分,第一个部分是针对writer基本功能一个扩展,即支持输出int,字符,以及字符数组,追加字符数组(包括字符串)等;第二个部分提供了写整形和长整形的基本方法;第三个部分是提供写基本数据类型数组的支持;第四个部分是提供写一个数字+一个字符的形式,比如数据+[,\],}]这种格式;第五个部分是提供写数据串,主是是针对字符串追加双引号或单引号(以支持标准json)。publicvoidwrite(intc)

  • charc)
  • charc[],intoff,85); font-weight:bold">intlen)
  • voidwrite(Stringstr,85); font-weight:bold">publicSerializeWriterappend(CharSequencecsq)
  • publicSerializeWriterappend(CharSequencecsq,85); font-weight:bold">intstart,85); font-weight:bold">intend)
  • publicSerializeWriterappend(
  • voidwriteInt(inti)
  • voidwriteLong(longi)
  • voidwriteBooleanArray(boolean[]array)
  • voidwriteShortArray(short[]array)
  • voidwriteByteArray(byte[]array)
  • voidwriteIntArray(int[]array)
  • voidwriteIntArray(Integer[]array)
  • voidwriteLongArray(long[]array)
  • voidwriteIntAndChar(inti,85); font-weight:bold">voidwriteLongAndChar(longi,85); font-weight:bold">voidwriteStringWithDoubleQuote(Stringtext)
  • voidwriteKeyWithDoubleQuote(Stringtext)
  • voidwriteStringWithSingleQuote(Stringtext)
  • voidwriteStringArray(String[]array)
  • voidwriteKeyWithSingleQuote(Stringtext)
  • voidwriteKeyWithDoubleQuoteIfHashSpecial(Stringtext)
  • voidwriteKeyWithSingleQuoteIfHashSpecial(Stringtext)
  • 关于FastJson 序列化java bean中的属性顺序问题(alibaba.fastjson)fastjson序列化指定字段的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于alibaba fastjson (json 序列化器) 序列化部分源码解析 - 2 - 性能优化 A、alibaba fastjson (json 序列化器) 序列化部分源码解析 - 2 - 性能优化 B、Alibaba fastjson 序列化与反序列化、alibaba fastjson(json序列化器)序列化部分源码解析- Java综合的相关知识,请在本站寻找。

    本文标签: