fastjson json转对象有一个问题,我不知道是bug,还是我不会用,请高人指点

后使用快捷导航没有帐号?
查看: 3513|回复: 4
Fastjson技术内幕
JSON协议使用方便,越来越流行。JSON的处理器有很多,为什么需要再写一个呢?因为我们需要一个性能很好的JSON Parser,希望JSON Parser的性能有二进制协议一样好,比如和protobuf一样,这可不容易,但确实做到了。有人认为这从原理上就是不可能的,但是计算机乃实践科学,看实际的结果比原理推导更重要。
7 e* j1 A: M* D$ L
这篇文章告诉大家: " {2 q6 d% p7 k
* Fastjson究竟有多快
* 为什么Fastjson这么快 . e5 \" p9 h9 t1 o& Z5 N9 W
* 你能用Fastjson来做什么! . i&&u& Y: j. N. g! K
* 如何获得fastjson? & k' ~( e: v5 F& J
/ R- n&&x; P# n- m6 w- ^
首先,Fastjson究竟有多快? - z% Z& _5 b. `+ d
我们看一下使用提供的程序进行测试得到的结果:
&&序列化时间 反序列化时间 大小 压缩后大小 java序列化8654 43787 889 541hessian6725 10460 501 313 protobuf2964 1745 239 149 thrift 3177 1949 349 197 avro 3520 1948 221 133 json-lib 45788 149741 485 263 jackson 3052 4161 503 271 fastjson 2595 1472 468 251 " H# ]' z. X6 P- f
这是一个468bytes的JSON Bytes测试,从测试结果来看,无论序列化和反序列化,Fastjson超越了protobuf,可以当之无愧fast! 它比java deserialize快超过30多倍,比json-lib快100倍。由于Fastjson的存在,你可以放心使用json统一协议,达到文本协议的可维护性,二进制协议的性能。 * J- d" {5 `4 G3 Q
JSON处理主要包括两个部分,serialize和deserialize。serialize就是把Java对象变成JSON String或者JSON Bytes。Deserialize是把JSON String或者Json Bytes变成java对象。其实这个过程有些JSON库是分三部分的,json string &--& json tree &--& java object。Fastjson也支持这种转换方式,但是这种转换方式因为有多余的步骤,性能不好,不推荐使用。
- e) f4 @6 B' k4 r* ]/ z
为什么Fastjson能够做到这么快? / R, X# v7 B7 x7 O' `
一、Fastjson中Serialzie的优化实现 0 U# e* x7 O7 z2 p) D7 j
1、自行编写类似StringBuilder的工具类SerializeWriter。 1 Z* V/ L6 d$ K+ T2 A
把java对象序列化成json文本,是不可能使用字符串直接拼接的,因为这样性能很差。比字符串拼接更好的办法是使用java.lang.StringBuilder。StringBuilder虽然速度很好了,但还能够进一步提升性能的,fastjson中提供了一个类似StringBuilder的类com.alibaba.fastjson.serializer.SerializeWriter。 - M" |&&u9 H% D9 D7 I
SerializeWriter提供一些针对性的方法减少数组越界检查。例如public void writeIntAndChar(int i, char c) {},这样的方法一次性把两个值写到buf中去,能够减少一次越界检查。目前SerializeWriter还有一些关键的方法能够减少越界检查的,我还没实现。也就是说,如果实现了,能够进一步提升serialize的性能。
2、使用ThreadLocal来缓存buf。
这个办法能够减少对象分配和gc,从而提升性能。SerializeWriter中包含了一个char[] buf,每序列化一次,都要做一次分配,使用ThreadLocal优化,能够提升性能。
3、使用asm避免反射 9 K+ G&&r&&}/ o
获取java bean的属性值,需要调用反射,fastjson引入了asm的来避免反射导致的开销。fastjson内置的asm是基于objectweb asm 3.3.1改造的,只保留必要的部分,fastjson asm部分不到1000行代码,引入了asm的同时不导致大小变大太多。 5 |% @7 N& F( M9 G7 R
使用一个特殊的IdentityHashMap优化性能。
fastjson对每种类型使用一种serializer,于是就存在class -& JavaBeanSerizlier的映射。fastjson使用IdentityHashMap而不是HashMap,避免equals操作。我们知道HashMap的算法的transfer操作,并发时可能导致死循环,但是ConcurrentHashMap比HashMap系列会慢,因为其使用volatile和lock。fastjson自己实现了一个特别的IdentityHashMap,去掉transfer操作的IdentityHashMap,能够在并发时工作,但是不会导致死循环。 2 m( L4 `$ {&&D' ~9 I1 r: f+ \) B; m9 U
1 }+ w& @8 o! B5 D7 |&&l
5、缺省启用sort field输出 : M" E2 A$ h8 R8 C&&t9 s# N+ B
json的object是一种key/value结构,正常的hashmap是无序的,fastjson缺省是排序输出的,这是为deserialize优化做准备。
6、集成jdk实现的一些优化算法 1 ^2 N' L( e- T" D; T
在优化fastjson的过程中,参考了jdk内部实现的算法,比如int to char[]算法等等。
2 ]& a% ?7 A$ {' I% i( E5 ~
二、fastjson的deserializer的主要优化算法
deserializer也称为parser或者decoder,fastjson在这方面投入的优化精力最多。
1、读取token基于预测。 1 |! k" N' N. ~/ R! y
所有的parser基本上都需要做词法处理,json也不例外。fastjson词法处理的时候,使用了基于预测的优化算法。比如key之后,最大的可能是冒号&:&,value之后,可能是有两个,逗号&,&或者右括号&}&。在com.alibaba.fastjson.parser.JSONScanner中提供了这样的方法: : t( M6 ]" n8 d) [0 s+ S
[url=][/url]
3 C6 u) O/ C, E) e/ ~
public2 Q# ~# y* U2 V
void nextToken(int expect) {& &
for (;;) {& &
switch (expect) {& &! D4 b' t& |) h& X1 S: u% N7 [
MA: //&&% p2 F2 ?8 @& G5 k" L
if (ch == ',') {& && && && && && && &&&token = MA;& && && && && && && &&&ch = buf[++bp];& &
& && && && && && & }& &% V- q+ z9 N4 S* z
if (ch == '}') {& && && && && && && &&&token = JSONToken.RBRACE;& && && && && && && &&&ch = buf[++bp];& &
& && && && && && & }& &
从上面摘抄下来的代码看,基于预测能够做更少的处理就能够读取到token。
, P- [6 a) ^3 Q
if (ch == ']') {& && && && && && && &&&token = JSONToken.RBRACKET;& && && && && && && &&&ch = buf[++bp];& &
& && && && && && & }& &
if (ch == EOI) {& && && && && && && &&&token = JSONToken.EOF;& &
& && && && && && & }& &! X2 W* C, w4 U, g7 h7 a* L. K
// ... ... & & }& &}&&" ?; l% t* R0 h8 Q5 P4 B
2、sort field fast match算法
fastjson的serialize是按照key的顺序进行的,于是fastjson做deserializer时候,采用一种优化算法,就是假设key/value的内容是有序的,读取的时候只需要做key的匹配,而不需要把key从输入中读取出来。通过这个优化,使得fastjson在处理json文本的时候,少读取超过50%的token,这个是一个十分关键的优化算法。基于这个算法,使用asm实现,性能提升十分明显,超过300%的性能提升。
[url=][/url]# M2 S0 M) R: Y0 i. M( `
1 C; C& E+ X9 \# H
{ &id& : 123, &name& : &魏加流&, &salary& : 56789.79}& &&&------& && &--------& && && & ----------& &
{ &id& : 123, &name& : &魏加流&, &salary& : 56789.79}&&------& && &--------& && && & ----------&&
在上面例子看,虚线标注的三个部分是key,如果key_id、key_name、key_salary这三个key是顺序的,就可以做优化处理,这三个key不需要被读取出来,只需要比较就可以了。
1 Z/ i/ I4 ?% }
这种算法分两种模式,一种是快速模式,一种是常规模式。快速模式是假定key是顺序的,能快速处理,如果发现不能够快速处理,则退回常规模式。保证性能的同时,不会影响功能。 ; V1 @1 T3 V0 n8 @8 T9 d6 T
在这个例子中,常规模式需要处理13个token,快速模式只需要处理6个token。 8 d0 E/ k&&o6 H
&&\) u4 i% e% [- n) l$ A
实现sort field fast match算法的代码在这个类com.alibaba.fastjson.parser.deserializer.ASMDeserializerFactory 2 }- j- g0 ^% }) S4 K
,是使用asm针对每种类型的VO动态创建一个类实现的。 1 q6 A2 l9 r% M5 O&&F# |6 @1 p$ F$ t
这里是有一个用于演示sort field fast match算法的代码:
[url=][/url]
8 b- h1 _&&Z" V
// 用于快速匹配的每个字段的前缀
char[] size_& &= &\&size\&:&.toCharArray();& &" _7 V" m# V$ Z&&e2 [&&X2 _2 j
char[] uri_& & = &\&uri\&:&.toCharArray();& &
char[] titile_ = &\&title\&:&.toCharArray();& &0 b$ e9 P( @1 A2 \* B
char[] width_&&= &\&width\&:&.toCharArray();& &7 F- h( [! K" p&&c/ P/ P2 Q
char[] height_ = &\&height\&:&.toCharArray();& &
// 保存parse开始时的lexer状态信息
int mark = lexer.getBufferPosition();& &
char mark_ch = lexer.getCurrent();& &
int mark_token = lexer.token();& &8 u0 x$ ]; q&&D1 A- \) j
int height = lexer.scanFieldInt(height_);& &/ Z- W% ?# L* e
if (lexer.matchStat == JSONScanner.NOT_MATCH) {& &
// 退出快速模式, 进入常规模式 & & lexer.reset(mark, mark_ch, mark_token);& &
return (T) super.deserialze(parser, clazz);& &}& &String value = lexer.scanFieldString(size_);& &
if (lexer.matchStat == JSONScanner.NOT_MATCH) {& &2 {1 [1 t: w4 M&&Y4 t$ S6 [
// 退出快速模式, 进入常规模式 & & lexer.reset(mark, mark_ch, mark_token);& &7 v5 a) t0 F4 j" ]$ W% b5 R! n% y2 w
return (T) super.deserialze(parser, clazz);& &}& &Size size = Size.valueOf(value);& &* s' W/ D# n/ j! g* a' K* a( S
// ... ... % E. d( J* h9 j! q+ t" Z
// batch set Image image = new Image();& &image.setSize(size);& &image.setUri(uri);& &image.setTitle(title);& &image.setWidth(width);& &image.setHeight(height);& &
return (T)&&
3、使用asm避免反射 9 T0 m) T3 G3 m# ?
deserialize的时候,会使用asm来构造对象,并且做batch set,也就是说合并连续调用多个setter方法,而不是分散调用,这个能够提升性能。
' n5 k% T7 A) U& n" ~3 {
4、对utf-8的json bytes,针对性使用优化的版本来转换编码。
这个类是com.alibaba.fastjson.util.UTF8Decoder,来源于JDK中的UTF8Decoder,但是它使用ThreadLocal Cache Buffer,避免转换时分配char[]的开销。
ThreadLocal Cache的实现是这个类com.alibaba.fastjson.util.ThreadLocalCache。第一次1k,如果不够,会增长,最多增长到128k。 * ~5 O# N" t( o1 ^$ n( c5 G
[url=][/url]1 c& Z4 Y) h0 E# \& h
) R4 X5 G6 n6 D% O" }
//代码摘抄自com.alibaba.fastjson.JSON $ W" O7 D3 K2 d# E) I' ^
final &T& T parseObject(byte[] input, int off, int len, CharsetDecoder charsetDecoder, Type clazz,& && && && && && && && && && && && && &&&Feature... features) {& && & charsetDecoder.reset();& &% U$ x- [9 h" w# `
int scaleLength = (int) (len * (double) charsetDecoder.maxCharsPerByte());& &; d0 x) p- f/ B
char[] chars = ThreadLocalCache.getChars(scaleLength); // 使用ThreadLocalCache,避免频繁分配内存 & & ByteBuffer byteBuf = ByteBuffer.wrap(input, off, len);& && & CharBuffer charByte = CharBuffer.wrap(chars);& && & IOUtils.decode(charsetDecoder, byteBuf, charByte);& &! Q+ q& v% N7 [
int position = charByte.position();& &
return (T) parseObject(chars, position, clazz, features);& &}&&5 r- d% V* ^5 G# P1 s
6、symbolTable算法。
我们看xml或者javac的parser实现,经常会看到有一个这样的东西symbol table,它就是把一些经常使用的关键字缓存起来,在遍历char[]的时候,同时把hash计算好,通过这个hash值在hashtable中来获取缓存好的symbol,避免创建新的字符串对象。这种优化在fastjson里面用在key的读取,以及enum value的读取。这是也是parse性能优化的关键算法之一。 $ c# T; V4 F% ?* R7 D/ L6 @% E8 E
' c! m- r8 S" R. C' y
以下是摘抄自JSONScanner类中的代码,这段代码用于读取类型为enum的value。 ) z" ^7 X$ S2 _6 I
[url=][/url]
' `5 ?% |5 h8 v: a: ~$ A% y
int hash = 0;& && V&&U2 ^' a0 a1 Y5 N
for (;;) {& && & ch = buf[index++];& &* B- h- e&&I' J& y$ u
if (ch == '\&') {& && && &&&bp =& &, p" i9 o1 x6 t9 v( l
this.ch = ch = buf[bp];& && && &&&strVal = symbolTable.addSymbol(buf, start, index - start - 1, hash); // 通过symbolTable来获得缓存好的symbol,包括fieldName、enumValue
& && & }& && & hash = 31 * hash + // 在token scan的过程中计算好hash 3 r) E. _; s/ E/ h, ?4 n
// ... ... }&&4 ~0 I( r0 m6 d3 n6 ?- g
int hash = 0;for (;;) {& & ch = buf[index++];& & if (ch == '\&') {& && &&&bp =& && &&&this.ch = ch = buf[bp];& && &&&strVal = symbolTable.addSymbol(buf, start, index - start - 1, hash); // 通过symbolTable来获得缓存好的symbol,包括fieldName、enumValue& && &&&& & }& && &&&hash = 31 * hash + // 在token scan的过程中计算好hash& & // ... ...}
9 v' x! L7 Z/ y6 ^4 L; o8 u
我们能用fastjson来作什么?
1、替换其他所有的json库,java世界里没有其他的json库能够和fastjson可相比了。 * Q0 v+ S, ?9 h1 t0 Z* H' y# [
2、使用fastjson的序列化和反序列化替换java serialize,java serialize不单性能慢,而且体制大。 ' h5 f/ O7 v7 e&&B
3、使用fastjson替换hessian,json协议和hessian协议大小差不多一样,而且fastjson性能优越,10倍于hessian
4、把fastjson用于memached缓存对象数据。 7 ?&&M/ K+ w6 x2 M+ |
) P+ T, V0 [& l&&Z- b&&R- r) t
( M, D' m! a+ r$ }* [
如何获得fastjson $ i0 O* P% ?" y
h3. 官方网站 ) _) F- V% |( x! N9 z" O& w
Fastjson是开源的,基于Apache 2.0协议。你可以在官方网站了解最新信息。
9 a5 H5 E8 c/ P7 G0 J
- H; M4 T+ n) E/ p
* Maven仓库
. J8 Q( w: d, w3 \
{code} * v# p3 B0 Q$ x1 b
&dependency&
& &&&&groupId&com.alibaba&/groupId&
& &&&&artifactId&fastjson&/artifactId&
& &&&&version&1.1.2&/version&
&/dependency&
{code} ( A: @) x/ S7 u" r4 \
Downlaods : {2 j0 {! s, H9 i9 L9 E&&e
Subversion :项目中使用 fastjson 来处理 json 格式,当前使用的版本为1.1.37。在和其它系统交互时,将一个json串传给了对方,原值为5.0,json 处理后格式为:{"dou", 5}; 结果对方处理该串报错了, 原因是他将串整理转成 Map ,在取值时强制转为了 Double ,因为拿到的值转化是 Integer 类型,强转肯定异常了。 简单的做法应该通过 Double.valueOf(value) &进行处理。但无奈合作方不愿意处理。 于是测试了下fastjson处理这个串时,通过以下做处理, 输出的结果为 {"dou", 5}。
JSONObject jsonObject = new JSONObject();
Double dou = new Double(5.0);
jsonObject.put("dou", dou);
System.out.println(JSON.toJSONString(jsonObject));
想要输出{"dou",5.0} 怎么办, 跟踪了下源码,发现在&DoubleSerializer 的 write 方法中,判断了结尾如果是.0 就截掉了。
doubleText = Double.toString(doubleValue);
if(doubleText.endsWith(".0")) {
doubleText = doubleText.substring(0, doubleText.length() - 2);
那想要的格式怎么办,可以通过自定义 filter 方式实现,:
ValueFilter filter = new ValueFilter() {
public Object process(Object object, String name, Object value) {
if(value instanceof BigDecimal || value instanceof Double || value instanceof Float){
return new BigDecimal(value.toString());
= JSON.toJSONString(jsonObject, filter, new SerializerFeature[0]);
以上可以完美解决。后来想有没有跟好的方法呢。 于是网上搜索了一下,大多数都是这种做法,并且有人认为这是一个bug,于是突然想有没有可能 wenshao 会处理一下,于是在 github 找到 fastjson 的最新版本 1.2.23。 先修改 pom 文件,然后运行。发现即使不处理也能输出了&{"dou",5.0} 。 于是 debug 进去原来对&DoubleSerializer 进行了重写,并在 write 方法中原来处理格式的地方修改为如下:
if (decimalFormat == null) {
out.writeDouble(doubleValue, true);
String doubleText = decimalFormat.format(doubleValue);
out.write(doubleText);
//out SerializeWriter
String doubleText = Double.toString(doubleValue);
if (isEnabled(SerializerFeature.WriteNullNumberAsZero) && doubleText.endsWith(".0")) {
doubleText = doubleText.substring(0, doubleText.length() - 2);
即,以上粉色代码调用&SerializeWriter 的writeDouble 方法, 看绿色部分。 同时判断了 SerializerFeature.WriteNullNumberAsZero 和 结尾是否为 .0
就是通过这个解决了 double 精度的正常输出。 &在使用 1.2.23时如果想输出{"dou", 5}, 可以通过设置&SerializerFeature.WriteNullNumberAsZero 实现。
System.out.println(JSON.toJSONString(jsonObject, SerializerFeature.WriteNullNumberAsZero));
啰嗦了这么,希望通过升级版本解决同样遇到这样问题的小伙伴。
阅读(...) 评论()2016年7月 Oracle大版内专家分月排行榜第二2016年6月 Oracle大版内专家分月排行榜第二
本帖子已过去太久远了,不再提供回复功能。

我要回帖

更多关于 fastjson 1.2.28 bug 的文章

 

随机推荐