关于fastjson 的使用:
转载地址:http://www.iteye.com/topic/1098058
首先可以了解下fastjson序列化的实现过程:
从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信息。
- SerializeWriterout=newSerializeWriter();
- JSONSerializerserializer=newJSONSerializer(out);
- serializer.write(object);
- 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)
五个部分的方法,每个部分都有其特殊的作用和意义,针对最常用的数字和字符串作了特别的对待。
在实现上面,SerializeWriter使用了一个内部的字符数组作为数据的储存器,同时使用了一个计数器计算当前存储的字符量。既然使用了字符数组,那么肯定有相关的操作,如字符扩容等。整个写数据的过程,其实就是往这个字符数组追加数据的过程,需要考虑只是如何追加数据的问题,即上面所列出的这么多些方法。在最终写完数据之后,即可将这个字符数组转为我们所需要的字符串了。
对象序列化入口
JsonSerializer,准备地讲,这个类不应该叫这个名字,因为它与其它的对象序列化器相混淆了。这只是一个提供对象序列化的一个入口;同时,它持有所有具体负责对象序列化工作类的引用。将这些序列化器集中起来,需要用到哪个对象序列化器时,就取出这个序列化器,并调用相应的序列化方法。 既然是对象序列化入口,它就需要关注两个事情。一是我们究竟有哪些序列化器可以使用,二是对于一个对象,应该使用哪一个序列化器来进行工作。对于这两个问题,JsonSerializer内部持有一个JSONSerializerMap的属性,即表示应该序列化的对象类型和对应的序列化器的一个映射。我们来看默认的构造方法,它使用了默认的全局对象类型和对象序列化器映射:
Java代码
publicJSONSerializer(SerializeWriterout){
this(out,JSONSerializerMap.getGlobalInstance());
}
这时使用了全局的一个对象序列化器映射,加上后面在getObjectWriter中追加的对象序列化器映射。在整个jsonSerializer中,可以使用的对象序列化器有以下这些:
put(Boolean.class,BooleanSerializer.instance);
put(Byte. put(Short. put(Integer. put(Long. put(Float. put(Double. put(BigDecimal. put(BigInteger. put(String. put(byte[].short[].int[].long[].float[].double[].boolean[]. put(Integer[]. put(String[]. put(Object[]. put(Class.
put(AtomicBoolean. put(AtomicInteger. put(AtomicLong. put(AtomicReference. put(AtomicIntegerArray. put(AtomicLongArray.//jmx
put(CompositeData. put(CompositeDataSupport. put(TabularData. put(TabularDataSupport. put(ObjectName. put(SimpleType.//在执行过程中追加部分
mapping.put(clazz,MapSerializer.instance);
newArraySerializer(compObjectSerializer));
newExceptionSerializer(clazz));
newJavaBeanSerializer(clazz));
这些序列化器,覆盖了基本数据,字符串类型,日期,以及集合,map,以及javaBean的所有序列化器。因为不存在没有匹配不了的序列化器。既然有个序列化器,就可以执行序列化工作了。即到了序列化入口应该做的工作了。
Class<?>clazz=object.getClass();
ObjectSerializerwriter=getObjectWriter(clazz);
writer.write(this,object);
首先获得当前序列化对象所在的类型,再根据类型取得相对应的序列化器,最后使用序列化器进行正式的序列化工作。
序列化过程
正如上面所说,进入序列化工作之后,即是针对每一种类型进行序列化处理了。该序列化工作使用了统一的方法,即实现了统一的序列化方法:
voidwrite(JSONSerializerserializer,Objectobject)throwsIOException
该方法在抽象类(可以说是接口)ObjectSerializer中定义,即所有的序列化器都继承了此类,并实现了此方法用于处理不同的情形。对于上层调用(如JsonSerializer),不需要考虑每一个类型的序列化工作是如何实现的,只需要针对不同的类型找到正确的序列化器,进行序列化工作即可。
对于一个序列化器,通常的工作,是首先取得当前的数据储存容器,然后根据不同的对象类型,将对象输出到outWriter中即可。比如一个序列化实现IntergerSerializer,它的实现如下:
SerializeWriterout=serializer.getWrier();
Integervalue=(Integer)object;
out.writeInt(value.intValue());
这 样即完成了一个完整的序列化工作。当然,对于复杂的数据类型,在实现过程中,可能需要递归地调用JsonSerializer的序列化工作,这得归结于如何处理不同的对象类型了。比如处理一个对象集合时,除需要处理集合本身之外,还需要处理集合中的每一个对象,这时又是一个解析过程。由于使用了同一个jsonSerializer,所以在进行数据处理时,输出的数据会按照在解析过程中的顺序,顺序地写入到outWriter中,这样即保证了数据的正确性。
总结
整个解析过程,相对来说,比较地简单。因为,这个解析工作从原理上来讲,也并不复杂。困难地在于,如何处理不同的数据类型,以及在处理过程中如何保证处理的效率。这即是fastjson之所以产生的原因。 本篇从整个结构出发,对fastjson中的json序列化过程有了一个初步的理解,让大家都能够很好地正解fastjson,包括fastjson本身在实现上可能存在的不合理情况。在下一篇中,就效率实现上的两个重要方面(输出效率和解析过程)分别进行解析。
接上篇,在论述完基本概念和总体思路之后,我们来到整个程序最重要的部分-性能优化。之所以会有fastjson这个项目,主要问题是为了解决性能这一块的问题,将序列化工作提高到一个新的高度。我们提到,性能优化主要有两个方面,一个如何将处理后的数据追加到数据储存器,即outWriter中;二是如何保证处理过程中的速度。 本篇从第一个性能优化方面来进行解析,主要的工作集中在类SerializeWriter上。
首先,类的声明,继承了Writer类,实现了输出字符的基本功能,并且提供了拼接数据的基本功能。内部使用了一个buf数组和count来进行计数。这个类的实现结果和StringBuilder的工作模式差不多。但我们说为什么不使用StringBuilder,主要是因为StringBuilder没有针对json序列化提出更加有效率的处理方式,而且单就StringBuilder而言,内部是为了实现字符串拼接而生,因为很自然地使用了更加能够读懂的方式进行处理。相比,serializeWriter单处理json序列化数据传输,功能单一,因此在某些方面更加优化一些。 在类声明中,这里有一个优化措施(笔者最开始未注意到,经作者指出之后才明白)。即是对buf数组的缓存使用,即在一次处理完毕之后,储存的数据容器并不销毁,而是留在当前线程变量中。以便于在当前线程中再次序列化json时使用。源码如下:
public SerializeWriter(){
buf=bufLocal.get();
if(buf==null){
buf=newchar[1024];
}else{
bufLocal.set(null);
}
}
在初始构造时,会从当前线程变量中取buf数组并设置在对象属性buf中。而在每次序列化完成之后,会通过close方法,将此buf数组再次绑定在线程变量当中,如下所示:
/**
*Closethestream.Thismethoddoesnotreleasethebuffer,sinceitscontentsmightstillberequired.Note:
*Invokingthismethodinthisclasswillhavenoeffect.
*/
voidclose(){
bufLocal.set(buf);
}
当然,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函数。
实现解析
基本功能 基本功能有以下几个函数:
charc)
其中第一个函数,可以忽略,可以理解为实现writer中的writ(int)方法,在具体应用时未用到此方法。第2个方法和第7个方法为写单个字符,即往buf数组中写字符。第3,4,5,6,均是写一个字符数组(字符串也可以理解为字符数组)。因此,我们单就字符数组进行分析,源码如下:
intlen){
intnewcount=count+len;
//扩容计算
System.arraycopy(c,off,buf,count,len);
count=newcount;
从上注释可以看出,其处理流程和我们所说的标准处理逻辑一致。在处理字符拼接时,尽量使用最快的方法,如使用System.arrayCopy和字符串中的getChars方法。另外几个方法处理逻辑与此方法相同。 警告:不要在正式应用中对有存在特殊字符的字符串(无特殊字符的字符串除外)使用以上的输出方式,请使用第5组方式进行json输出。对于字符数组的处理在以上处理方式中不会对特殊字符进行处理。如字符串 3"'4,在使用以上方式输出时,只会输出 3"'4,其中的转义字符在转化为toChar时被删除掉。 因此,在实际处理中,只有字符数组会使用以上方式进行输出。不要将字符串与字符数组相混合。字符数组不考虑转义问题,而字符串需要考虑转义。
整形和长整形
方法如下:
longi)
这两个方法,按照我们的逻辑,首先需要将整性和长整性转化为字符串(无特殊字符),然后以字符数组的形式输出即可。在进行处理时,主要参考了Integer和Long的toString实现方式和长度计算。首先看一个实现:
inti)throwsIOException{
if(i==Integer.MIN_VALUE){
write("-2147483648");
return;
intsize=(i<0)?IOUtils.stringSize(-i)+1:IOUtils.stringSize(i);
intnewcount=count+size;
IOUtils.getChars(i,newcount,buf);
//最终定count值
以上首先看特殊数字的处理,因为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,char[] buf)方法,这里也不再叙述。
基本类型数组
long[]array)
数组的形式,主要是将数组的每一部分输出出来,即可。在输出时,需要输出前缀“[”和后缀“]”以及每个数据之间的“,“。按照我们的逻辑,首先还是计算长度,其次是准备空间,再者是写数据,最后是定count值。因此,我们参考一个实现:
int[]array)int[]sizeArray=int[array.length];
inttotalSize=2;
for(inti=0;i<array.length;++i){
if(i!=0){totalSize++;}
intval=array[i];
//针对每一个数字取长度,此处有部分删除。分别针对minValue和普通value运算
intsize=(val<0)?IOUtils.stringSize(-val)+1:IOUtils.stringSize(val);
sizeArray[i]=size;
totalSize+=size;
buf[count]='[';
intcurrentSize=count+1;
0){buf[currentSize++]=',';}
//追加当前数字的字符形式,分别针对minValue和普通数字作处理
currentSize+=sizeArray[i];
IOUtils.getChars(val,currentSize,buf);
buf[currentSize]=']';
//最终count定值
此处有关于性能优化的地方,主要有几个地方。首先将minValue和普通数字分开计算,以避免可能出现的问题;在计算长度时,尽量调用前面使用stringToSize方法,此方法最快;在进行字符追加时,利用getChars方法进行处理。 对于仍有优化的地方,比如对于boolArray,在处理时,又有了特殊优化,主要还是在上面的两点,计算长度时,尽量地快,以及在字符追加时也尽量的快。以下为对于boolean数据的两个优化点:
//计算长度,直接取值,不需要进行计算
if(val){
size=4;
else{}
//追加字符时,不需要调用默认的字符拼接,直接手动拼接,减少中间计算量
booleanval=array[i];
//System.arraycopy("true".toCharArray(),4);
buf[currentSize++]='t';
buf[currentSize++]='r';
buf[currentSize++]='u';
buf[currentSize++]='e';
else{}
数字+字符输出
charc)
以上两个方法主要在处理以下情况下使用,在不知道要进行序列化的对象的长度的情况下,要尽量避免进行buf数据扩容的情况出现。尽管这种情况很少发生,但还是尽量避免。特殊是在输出集合数据的情况下,在集合数据输出下,各个数据的长度未定,因此不能计算出总输出长度,只能一个对象一个对象输出,在这种情况下,先要输出一个对象,然后再输出对象的间隔符或结尾符。如果先调用输出数据,再调用输出间隔符或结尾符,远不如将两者结合起来,一起进行计算和输出。 此方法基于以下一个事实:尽量在已知数据长度的情况下进行字符拼接,这样有利于快速的为数据准备数据空间。 在具体实现时,此方法只是减少了数据扩容的计算,其它方法与基本实现和组合是一致的,以writeIntAndChar为例:
charc)//minValue处理
//长度计算,长度为数字长度+字符长度
1:IOUtils.stringSize(i);
intnewcount0=count+size;
intnewcount1=newcount0+1;
//输出数字
buf[newcount0]=c;
count=newcount1;writeKeyWithSingleQuoteIfHashSpecial(Stringtext)
其中第1,2方法表示分别用双引号和单引号将字符串包装起来,第3,4方法表示在字符串输出完毕之后,再输出一个冒号,第5方法表示输出一个字符串数组,使用双引号包装字符串。第7,8方法未知(不明真相的方法?) 字符串是可以知道长度的,所以第一步确定长度即OK了。 在第一步扩容计算之后,需要处理一个在字符串中特殊的问题,即转义字符处理。如何处理转义字符,以及避免不必要的扩容计算,是必须要考虑的。在fastjson中,采取了首先将其认定为全非特殊字符,然后再一个个字符判断,对特殊字符再作处理的方法。在一定程序上避免了在一个个判断时,扩容计算的问题。我们就其中一个示例进行分析:
voidwriteStringWithDoubleQuote(Stringtext){
//null处理,直接追加null字符即可,不需要双引号
intlen=text.length();
intnewcount=count+len+//初始计算长度为字符串长度+2(即双引号)
//初步扩容计算
intstart=count+intend=start+len;
buf[count]='"';
text.getChars(0,len,start);
//初步定count值
/**以下代码为处理特殊字符*/
inti=start;i<end;++i){
charch=buf[i];
if(ch=='b'||ch=='n'||ch=='r'||ch=='f'||ch==''||ch=='/'||ch=='"'){
//这里需要修改count值,以及扩容判断,省略之
System.arraycopy(buf,i+1,0)">2,end-i-1);
buf[i]='';
buf[++i]=replaceChars[(int)ch];
end++;
buf[newcount-1]='"';
在处理字符串上,特殊的即在特殊字符上。因为在输出时,要输出时要保存字符串的原始模式,如"的格式,要输出时,要输出为 + "的形式,而不能直接输出为",后者在输出时就直接输出为",而省略了,这在js端是会报错的。
总结:
在针对输出优化时,主要利用了最有效率的手段进行处理。如针对数字和boolean时的处理方式。同时,在处理字符串时,也采取了先处理最常用字符,再处理特殊字符的形式。在针对某些经常碰到的场景时,使用了联合处理的手段(如writeIntAndChar),而不再是分开处理。 整个处理的思想,即是在处理单个数据时,采取最优方式;在处理复合数据时,避免扩容计算;尽量使用jdk中的方法,以避免重复轮子(可能轮子更慢)。
下一篇,从数据处理过程对源码进行分析,同时解析其中针对性能优化的处理部分。
前面讲了进行对象解析的两个方面,并讲了针对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)方法,参考其中的实现:
publicObjectSerializergetObjectWriter(Class<?>clazz){
ObjectSerializerwriter=mapping.get(clazz);
if(writer==if(Map.class.isAssignableFrom(clazz)){
elseif(List.if(Collection.if(Date.if(JSONAware.if(JSONStreamAware.if(clazz.isEnum()){
if(clazz.isArray()){
Class<?>componentType=clazz.getComponentType();
ObjectSerializercompObjectSerializer=getObjectWriter(componentType);
if(Throwable.newJavaBeanSerializer(clazz));
writer=mapping.get(clazz);
returnwriter;
首先,取对象解析器是由一个类型为JSONSerializerMap的对象mapping中取。之所以使用此类型,这是性能优化的一部分,此类型并不是一个完整的map类型,只是实现了一个类似map的操作的类型。其内部并没有使用基于equals的比较方式,而是使用了System.identityHashCode的实现。对于一个对象,其identityHashCode的值是定值,而对于一个类型,在整个jvm中只有一个,因此这里使用基于class的identityHashCode是可以了,也避免了使用class的euqlas来进行比较。 接着再根据每一个类型从mapping中取出相对应的解析器。首先从继承而来的全局解析器取得解析器,如果对象不属于第1,4类型,而开始进入以下的if else阶段。 我们从上面的源码中,可以看出解析器主要分为两个部分,一个是与解析类型相关的,一个是无关的。比如对于第5,7类型,其中最5类型是集合类型,由于不知道集合类型中的具体类型(因为存在继承关系),所以类型无关。对于第8,9类型,其中第8类型为对象数组类型,对于对象数组,数组中的每一个对象的类型都是确定的,且整个数组只有一种类型,因此可以确定其类型,这时候就要使用类型相关解析器了,对于第9类型,需要使用解析对象的类型来确定相对应的javaBean属性,因此是类型相关。 另外一个确定解析器的过程当中,使用了映射机制,即将当前解析器与对应的类型映射起来,以便下一次时使用。对于集合类型及子类型,由于当前类型并不是确定的List或Collection类型,因此将当前类型与集合解析器映射起来。对于对象类型,需要将当前类型传递给相对应的解析器,以确定具体的属性。
解析过程 解析方法由统一的接口所定义,每个不同的解析器只需要实际这个方法并提供各自的实现即可。此方法为write(JSONSerializer serializer,Object object),由ObjectSerializer提供。带两个参数,第一个参数,即为解析的起点类jsonSerializer,此类封装了我们所需要的outWriter类,需要时只需要从此类取出即可。第二个参数为我们所要解析的对象,在各个子类进行实现时,可将此对象通过强制类型转换,转换为所需要的类型即可。 具体的解析过程根据不同的数据类型不所不同,对于第1,2类型,由于在outWriter中均有相对应的方法,所以在具体实现时,只需要调用相应的outWriter方法即可,而不需要作额外的工作。比如对于字符串解析器,它的解析过程如下所示:
Stringvalue=(String)object;
if(serializer.isEnabled(SerializerFeature.UseSingleQuotes)){
out.writeStringWithSingleQuote(value);
out.writeStringWithDoubleQuote(value);
即首先,取得输出时所需要的outWriter,然后再根据配置决定是输出单引号+字符串还是双引号+字符串。
而对于其它并没有由outWriter所直接支持的类型而言,解析过程就相对于复杂一些。但是总的逻辑还是基于以下逻辑:
- 基于数据类型特点输出所特有的字符包装内容
- 基于数据类型特点转换为outWriter所能识别的内容
- 逐步解析,将对象解析产生的字符数组输出到outWriter中
只需此三步,即可完美的解析一个对象。因此,我们可以参考其中的一个具体实现,并讨论其中的具体优化措施。 在取得解析器方法getObjectWriter(Class<?> clazz)中,我们可以看到,对于集合类型中的Collection和List,fastjson是分开进行解析的。即两者在解析时在细微处有着不一样的实现。两者的区别在于List是有序的,可以根据下标对元素进行访问,对于常用List实现,ArrayList,使用下标访问子元素的价格为O1。这就是在fastJson中采取的一点优化措施,详细看以下实现代码:
final SerializeWriterout=serializer.getWrier();
List<?>list=(List<?>)object;
intsize=list.size();
intend=size-//此处定义为size-1,是因为对最后一位有特殊处理
//空集合判断,省略之
out.append('[');
/**以下代码使用get(X)方法访问下标,实现代码对于ArrayList实现有好处,对于LinkedList是否有好处,还待考虑*/
0;i<end;++i){
Objectitem=list.get(i);
//空值判断
Class<?>clazz=item.getClass();
if(clazz==Integer.class){
out.writeIntAndChar(((Integer)item).intValue(),',');
if(clazz==Long.//针对Long.class特殊优化,使用outWriter自带方法
longval=((Long)item).longValue();
out.writeLongAndChar(val,250); line-height:18px"> serializer.write(item);
out.append(',');
/**以下代码为最后一位优化,当为最后一位时,不再需要输出间隔符,而是输出后缀包装符
这里即在处理时,直接输出后缀,与前面输出间隔符相对应*/
Objectitem=list.get(end);
class){
']');
out.writeLongAndChar(((Long)item).longValue(),250); line-height:18px"> serializer.write(item);
out.append(']');
以下实现与collection相比不同的即在于处理中间元素与末尾元素的区别。相对于Collection,就不能使用以上的方法了,在实现上,就只能先输出前缀,再一个一个地处理里面的元素,最后输出后缀。此实现如下所示:
Collection<?>collection=(Collection<?>)object;
out.append('[');
booleanfirst=true;
for(Objectitem:collection){
if(!first){out.append(',');}
first=false;
//Integer.class和Long.class特殊处理
以上代码就是通常最常见的实现了。
相对于集合类型实现,map实现和javaBean实现相对来说,稍微复杂了一点。主要是输出key和value的问题。在fastjson中,key输出表现为使用outWriter的writeKey来进行输出,value输出则同样使用常规的输出。按照我们先前所说的逻辑,首先还是输出包装字符内容,即{,在末尾输出}。然后,再根据每个key-value映射特点,采取相对应的输出方式。 当然,对于map类型输出和javaBean输出还是不一样的。两者可以互相转换,但fastjson在输出时还是采取了不一样的输出方式。那么,我们以源代码来查看相应的实现:
Map<?,?>map=(Map<?,?>)object;
out.write('{');
Class<?>preClazz=null;
ObjectSerializerpreWriter=null;
for(Map.Entry<?,?>entry:map.entrySet()){
//此处有删除,即根据nameFilter和valueFilter针对key-value作转换处理
if(!first){out.write(',');}
serializer.writeFieldName(key);
Class<?>clazz=value.getClass();
if(clazz==preClazz){
preWriter.write(serializer,value);
/**此处则就需要从jsonSerializer中查找解析器,并输出了*/
preClazz=clazz;
preWriter=serializer.getObjectWriter(clazz);
out.write('}');
由上可以看出,map的实现还是按照我们常用的思路在进行。在性能优化处,采取了两个优化点,一是输出字段时,并不直接输出字段名,而是采取字段+冒号的方式一起输出,减少outWriter扩容计算。二是在查找value解析器时,尽量使用前一个value的解析器,避免重复查找。 相比map,javaBean的实现就相对更复杂。javaBean输出并不是采取key-value的方式,而是采取类似fieldSerializer的输出方式,即将属性名和值,组合成一个字段,一起进行输出。当然输出时还是先输出字段名,再输出值的实现。那么对于javaBean实现,首先要取得当前对象类型的所有可以输出的类型。 在fastjson实现中,并没有采取javaBean属性的读取方式,而是采取了使用getXXX和isXXX方法的读取模式,来取得一个类型的可读取属性。作为性能优化的一部分,读取属性的操作结果被缓存到了getter缓存器中。其实,并不是缓存到了getter缓存器中,只是该类型的javaBean序列化器对象被缓存到了jsonSerializer的对象类型-序列化器映射中。对于同一个类型,就不需要再次解析该类型的属性了。 有了相对应的字段,那么在实现时,就按照相应的字段进行输出即可。以下为实现代码:
out.append('{');;i<getters.length;++i){
FieldSerializergetter=getters[i];
ObjectpropertyValue=getter.getPropertyValue(object);
//省略中间nameFilter和valueFilter过滤处理
if(commaFlag){out.append(',0); padding:0px; margin:0px; width:auto; border:0px">//省略nameFilter和valueFilter过滤之后的输出处理
getter.writeProperty(serializer,propertyValue);
out.append('}'); |