关于fastjson序列化部分源码解析
关于fastjson 的使⽤:
转载地址:
⾸先可以了解下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信息。
Java代码
1. SerializeWriter out = new SerializeWriter();
2. JSONSerializer serializer = new JSONSerializer(out);
3. serializer.write(object);
4. String();
数据保存器(序列化输出容器)
SerializeWriter是⼀个⽤于储存在序列化过程中产⽣的数据信息,它与jdk中的StringBuiler有着类似的功能,即将不同的数据填充到此容器中。之所以不使⽤StringBuilder的原因之⼀在于StringBuilder没有提供⼀些特别为性能优化的⽅法,并且StringBuilder在处理过程中增加了多余的操作(如新分配对象)。该容器的主要功能就是接收不同的数据,并将这些数据存储到该内部的⼀个字符数组当中,同时记录字符总数。
既然充当了⼀个数据输出的⾓⾊,那么就可以往其中输⼊任何的数据,包括int,byte,short等基本类型和对应的包装类型,也包括⽇期数据,以及经常使⽤的字符串数据等。对于在这些数据类型之外的其它类
型,由于json的特殊结构,所有的⾼级类型均可以转化于这些基础类型的组织体,所以不需要再针对⾼级类型作处理了(这些是序列化器应该考虑的问题)。
⾸先对SerializeWriter的⽅法(输出和追加)作⼀个预览:
⽅法总共可以分五个部分,第⼀个部分是针对writer基本功能⼀个扩展,即⽀持输出int,字符,以及字符数组,追加字符数组(包括字符串)等;第⼆个部分提供了写整形和长整形的基本⽅法;第三个部分是提供写基本数据类型数组的⽀持;第四个部分是提供写⼀个数字+⼀个字符的形式,⽐如数据+[,\],}]这种格式;第五个部分是提供写数据串,主是是针对字符串追加双引号或单引号(以⽀持标准json)。
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)
8.
9. public void writeInt(int i)
10. public void writeLong(long i)
11.
12. public void writeBooleanArray(boolean[] array)
13. public void writeShortArray(short[] array)
14. public void writeByteArray(byte[] array)
15. public void writeIntArray(int[] array)
16. public void writeIntArray(Integer[] array)
17. public void writeLongArray(long[] array)
18.
19. public void writeIntAndChar(int i, char c)
20. public void writeLongAndChar(long i, char c)
21.
22. public void writeStringWithDoubleQuote(String text)
23. public void writeKeyWithDoubleQuote(String text)
24. public void writeStringWithSingleQuote(String text)
25. public void writeStringArray(String[] array)
26. public void writeKeyWithSingleQuote(String text)
27. public void writeKeyWithDoubleQuoteIfHashSpecial(String text)
28. public void writeKeyWithSingleQuoteIfHashSpecial(String text)
五个部分的⽅法,每个部分都有其特殊的作⽤和意义,针对最常⽤的数字和字符串作了特别的对待。
在实现上⾯,SerializeWriter使⽤了⼀个内部的字符数组作为数据的储存器,同时使⽤了⼀个计数器计算当前存储的字符量。既然使⽤了字符数组,那么肯定有相关的操作,如字符扩容等。整个写数据的过程,其实就是往这个字符数组追加数据的过程,需要考虑只是如何追加数据的问题,即上⾯所列出的这么多些⽅法。在最终写完数据之后,即可将这个字符数组转为我们所需要的字符串了。
对象序列化⼊⼝
JsonSerializer,准备地讲,这个类不应该叫这个名字,因为它与其它的对象序列化器相混淆了。这只是⼀个提供对象序列化的⼀个⼊⼝;同时,它持有所有具体负责对象序列化⼯作类的引⽤。将这些序列化器集中起来,需要⽤到哪个对象序列化器时,就取出这个序列化器,并调⽤相应的序列化⽅法。
既然是对象序列化⼊⼝,它就需要关注两个事情。⼀是我们究竟有哪些序列化器可以使⽤,⼆是对于⼀个对象,应该使⽤哪⼀个序列化器来进⾏⼯作。对于这两个问题,JsonSerializer内部持有⼀个JSONSerializerMap的属性,即表⽰应该序列化的对象类型和对应的序列化器的⼀个映射。我们来看默认的构造⽅法,它使⽤了默认的全局对象类型和对象序列化器映射:
Java代码
1. public JSONSerializer(SerializeWriter out){
2. this(out, GlobalInstance());
3. }
这时使⽤了全局的⼀个对象序列化器映射,加上后⾯在getObjectWriter中追加的对象序列化器映射。在整个jsonSerializer中,可以使⽤的对象序列化器有以下这些:
Java代码
1. put(Boolean.class, BooleanSerializer.instance);
2. put(Byte.class, ByteSerializer.instance);
3. put(Short.class, ShortSerializer.instance);
4. put(Integer.class, IntegerSerializer.instance);
5. put(Long.class, LongSerializer.instance);
6. put(Float.class, FloatSerializer.instance);
7. put(Double.class, DoubleSerializer.instance);
8. put(BigDecimal.class, BigDecimalSerializer.instance);
9. put(BigInteger.class, BigIntegerSerializer.instance);
10. put(String.class, StringSerializer.instance);
11. put(byte[].class, ByteArraySerializer.instance);
12. put(short[].class, ShortArraySerializer.instance);
13. put(int[].class, IntArraySerializer.instance);
14. put(long[].class, LongArraySerializer.instance);
15. put(float[].class, FloatArraySerializer.instance);
16. put(double[].class, DoubleArraySerializer.instance);
17. put(boolean[].class, BooleanArraySerializer.instance);
18. put(Integer[].class, IntegerArraySerializer.instance);
19. put(String[].class, StringArraySerializer.instance);
20. put(Object[].class, ObjectArraySerializer.instance);
21. put(Class.class, ClassSerializer.instance);
22.
23. // atomic
24. put(AtomicBoolean.class, AtomicBooleanSerializer.instance);
25. put(AtomicInteger.class, AtomicIntegerSerializer.instance);
26. put(AtomicLong.class, AtomicLongSerializer.instance);
27. put(AtomicReference.class, AtomicReferenceSerializer.instance);
28. put(AtomicIntegerArray.class, AtomicIntegerArraySerializer.instance);
29. put(AtomicLongArray.class, AtomicLongArraySerializer.instance);
30.
31. // jmx
32. put(CompositeData.class, CompositeDataSerializer.instance);
33. put(CompositeDataSupport.class, CompositeDataSerializer.instance);
34. put(TabularData.class, TabularDataSerializer.instance);
35. put(TabularDataSupport.class, TabularDataSerializer.instance);
36. put(ObjectName.class, ObjectNameSerializer.instance);
37. put(SimpleType.class, SimpleTypeSerializer.instance);
38.
39. //在执⾏过程中追加部分
40. mapping.put(clazz, MapSerializer.instance);
41. mapping.put(clazz, ListSerializer.instance);
42. mapping.put(clazz, CollectionSerializer.instance);
43. mapping.put(clazz, DateSerializer.instance);
44. mapping.put(clazz, JSONAwareSerializer.instance);
45. mapping.put(clazz, JSONStreamAwareSerializer.instance);
46. mapping.put(clazz, EnumSerializer.instance);
47. mapping.put(clazz, new ArraySerializer(compObjectSerializer));
48. mapping.put(clazz, new ExceptionSerializer(clazz));
49. mapping.put(clazz, new JavaBeanSerializer(clazz));
这些序列化器,覆盖了基本数据,字符串类型,⽇期,以及集合,map,以及javaBean的所有序列化器。因为不存在没有匹配不了的序列化器。既然有个序列化器,就可以执⾏序列化⼯作了。即到了序列化⼊⼝应该做的⼯作了。
Java代码
1. Class<?> clazz = Class();
2. ObjectSerializer writer = getObjectWriter(clazz);
3. writer.write(this, object);
⾸先获得当前序列化对象所在的类型,再根据类型取得相对应的序列化器,最后使⽤序列化器进⾏正式的序列化⼯作。
序列化过程
正如上⾯所说,进⼊序列化⼯作之后,即是针对每⼀种类型进⾏序列化处理了。该序列化⼯作使⽤了统⼀的⽅法,即实现了统⼀的序列化⽅法:
Java代码
1. void write(JSONSerializer serializer, Object object) throws IOException
该⽅法在抽象类(可以说是接⼝)ObjectSerializer中定义,即所有的序列化器都继承了此类,并实现了此⽅法⽤于处理不同的情形。对于上层调⽤(如JsonSerializer),不需要考虑每⼀个类型的序列化⼯作是如何实现的,只需要针对不同的类型到正确的序列化器,进⾏序列化⼯作即可。
对于⼀个序列化器,通常的⼯作,是⾸先取得当前的数据储存容器,然后根据不同的对象类型,将对象输出到outWriter中即可。⽐如⼀个序列化实现IntergerSerializer,它的实现如下:
Java代码
1. SerializeWriter out = Wrier();
2. Integer value = (Integer) object;
3. out.writeInt(value.intValue());
总结
整个解析过程,相对来说,⽐较地简单。因为,这个解析⼯作从原理上来讲,也并不复杂。困难地在于,如何处理不同的数据类型,以及在处理过程中如何保证处理的效率。这即是fastjson之所以产⽣的原因。
本篇从整个结构出发,对fastjson中的json序列化过程有了⼀个初步的理解,让⼤家都能够很好地正解fastjson,包括fastjson本⾝在实现上可能存在的不合理情况。在下⼀篇中,就效率实现上的两个重要⽅⾯(输出效率和解析过程)分别进⾏解析。
接上篇,在论述完基本概念和总体思路之后,我们来到整个程序最重要的部分-性能优化。之所以会有fastjson这个项⽬,主要问题是为了解决性能这⼀块的问题,将序列化⼯作提⾼到⼀个新的⾼度。我们提到,性能优化主要有两个⽅⾯,⼀个如何将处理后的数据追加到数据储存器,即outWriter中;⼆是如何保证处理过程中的速度。
本篇从第⼀个性能优化⽅⾯来进⾏解析,主要的⼯作集中在类SerializeWriter上。
⾸先,类的声明,继承了Writer类,实现了输出字符的基本功能,并且提供了拼接数据的基本功能。内部使⽤了⼀个buf数组和count来进⾏计数。这个类的实现结果和StringBuilder的⼯作模式差不多。但我
们说为什么不使⽤StringBuilder,主要是因为StringBuilder没有针对json序列化提出更加有效率的处理⽅式,⽽且单就StringBuilder⽽⾔,内部是为了实现字符串拼接⽽⽣,因为很⾃然地使⽤了更加能够读懂的⽅式进⾏处理。相⽐,serializeWriter单处理json序列化数据传输,功能单⼀,因此在某些⽅⾯更加优化⼀些。
在类声明中,这⾥有⼀个优化措施(笔者最开始未注意到,经作者指出之后才明⽩)。即是对buf数组的缓存使⽤,即在⼀次处理完毕之后,储存的数据容器并不销毁,⽽是留在当前线程变量中。以便于在当前线程中再次序列化json时使⽤。源码如下:
Java代码
1. public SerializeWriter(){
2. buf = (); // 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. }
fastjson怎么用当然,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,具体可以见相应函数或sureCapacityImpl函数。
实现解析
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论