Android学习笔记之Fast Json的使用
PS:最近这两天发现了Fast Json 感觉实在是强大..
学习内容:
1.什么是Fast Json
2.如何使用Fast Json
3.Fast Json的相关原理
4.Fast Json的优势,以及为什么推荐使用Fast Json
1.Fast Json的相关介绍
说道Json想必我们都不陌生,数据传输的两种形式之一,另一种就是我们的xml了.不过现在更多的还是使用基于Json的格式来进行数据传输,Java的api接口为我们提供了相关的api接口用于对Json数据进行处理,如何将数据打包成Json的格式,以及如何将Json数据解析成JavaBean.但是Java内部的api接口虽然可以满足我们的要求,但是往往能够满足要求的不一定就是最好的.
就来说说Fast Json吧.Fast Json是阿里创建的一款api接口,用于对Json的处理,Fast Json的性能是非常的良好的,解析的速度要超过其他的接口然而他的有点远远不止这些,我们来列举一下他的相关优点吧.
i.首先就是速度上.Fast Json的解析速度是非常高效的,速度快的原因就是使用了优化算法,因此他的速度要远远好于其他的api接口.
ii.没有依赖性,在JDK 5.0开始被正式的启用,支持Android,支持的数据类型也是非常的多.
多的废话我也就不多说了,写这篇文章的主要目的是推荐大家去使用Fast Json。
2.如何使用Fast Json
说道如何使用无非就是如何把现有的数据转化成Json的格式,以及如何将现有的Json格式的数据解析成我们想要的数据.说白了就是将数据封装和解析.
我们现在使用Java提供的现有的api接口也可以完成将数据封装成Json格式的数据,也可以将现有的Json格式数据解析成JsonObject和JsonArray,然后在我们的应用程序中去使用这些数据.但是关于这章我只说一说如何使用Fast Json对数据进行封装,以及如何将现有的Json格式的数据进行相关的解析.
还是来看一下Fast Json如何在应用当中去使用才是重要的.先说使用方式,然后在说相关的内部原理.
package com.example.FastJson; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; public class Fastjson { //将List集合中保存的数据转化成Json类型数据. public void ListToJson(List<User> list){ String text = JSON.toJSONString(list, true); System.out.println(text); } //将List集合中保存的数据生成多级Json数据.. public void ListToMutiJson(List<User> list){ String text = JSON.toJSONString(list,true); System.out.println(text); } //将数据封装成Json. public String toJsonString(){ User user = new User("1", "a", 18); String text = JSON.toJSONString(user); System.out.println("toJsonString:将JavaBean转化成Json数据"+text); return text; } //将现有的Json数据解析成JavaObject对象 public void toParseJson(String text){ JSONObject object = JSON.parseObject(text); System.out.println("toParseJson:将现有的Json解析成JavaObject"+object); } //将JavaBean转化成Json对象. public void JavaBeanToJson(User user){ JSONObject jsonObject = (JSONObject) JSON.toJSON(user); System.out.println("将JavaBean转化成Json对象"+jsonObject); } //将Json对象转化成JavaBean public void JsonToJavaBean(String text){ User user = (User) JSON.parseObject(text, User.class); System.out.println("将Json对象转化成JavaBean后的数据获取"+user.getId()+" "+user.getName()+" "+user.getAge()); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Fastjson fastjson = new Fastjson(); String test = fastjson.toJsonString(); fastjson.toParseJson(test); User user_1 = new User("2","b",19); fastjson.JavaBeanToJson(user_1); fastjson.JsonToJavaBean(test); List<User>lists = new ArrayList<User>(); User user = new User("1", "a", 18); lists.add(user); fastjson.ListToJson(lists); lists.clear(); User user2 = new User("2", "b", 19, new From("中国", "山东省", "淄博市", "张店区")); lists.add(user2); fastjson.ListToMutiJson(lists); } }
User.java和From.java就是两个简单的Java Bean.在这里我就不进行粘贴了..没有什么必要..最后这些数据都会被封装成Json格式的数据.封装其实并不是我们想要的..一般而言,我们主要还是针对Json的解析,通过服务器发送来的Json数据,客户端对其进行解析,然后获取到服务器的数据,才是最重要的..解析Json数据的方式一般就是通过使用提供的函数,将Json解析成JsonObject或者是JsonArray..然后再将其转化成JavaBean就可以了
说了这么多,我们就进行下一个过程,Fast Json解析Json的原理..
3.Fast Json解析Json的原理
说到原理就不得不看源码了,Fast Json的解析Json的方式源码有很多种,我就针对将数据解析成Java Bean来说吧.也是非常常用的一个函数..我们来看这个函数.
package com.example.parser; import org.json.JSONException; import org.json.JSONObject; import android.text.TextUtils; import com.alibaba.fastjson.JSON; import com.example.vo.Version; /** * @author 代码如风 * 版本解析器 * * */ public class VersionParser extends BaseParser<Version>{ //解析URL中的JSON数据,将JSON解析成Object /** * 1. parseObject 调用 --> public static final <T> T parseObject() * * @param String JSON数据 * @param Class class属性 * @param ParserConfig 解析参数对象实例化 * @param int 参数值 * @param Feature ??? * * */ @Override public Version parseJSON(String paramString) throws JSONException { // TODO Auto-generated method stub if(!TextUtils.isEmpty(paramString)){ JSONObject j = new JSONObject(paramString); String version = j.getString("version"); //调用函数,将现有的Json数据解析生成Java Bean return JSON.parseObject(version, Version.class); } return null; } }
那么我就针对JSON.parseObject(version, Version.class)这个函数做一下相关的解析,其他函数大家有兴趣的可以去研究研究,原理基本上都是差不多的.
public abstract class JSON implements JSONStreamAware, JSONAware{ public static final int DEFAULT_PARSER_FEATURE; public static final int DEFAULT_GENERATE_FEATURE; public static CharsetDecoder UTF8_CharsetEncoder; static {
//默认解析方式,将Feature的一些相关属性进行叠加.. int features = 0; features |= Feature.AutoCloseSource.getMask(); features |= Feature.InternFieldNames.getMask(); features |= Feature.UseBigDecimal.getMask(); features |= Feature.AllowUnQuotedFieldNames.getMask(); features |= Feature.AllowSingleQuotes.getMask(); features |= Feature.AllowArbitraryCommas.getMask(); features |= Feature.SortFeidFastMatch.getMask(); features |= Feature.IgnoreNotMatch.getMask(); DEFAULT_PARSER_FEATURE = features;
//默认生成的一些属性. int features = 0; features |= SerializerFeature.QuoteFieldNames.getMask(); features |= SerializerFeature.SkipTransientField.getMask(); features |= SerializerFeature.SortField.getMask(); DEFAULT_GENERATE_FEATURE = features; UTF8_CharsetEncoder = new UTF8Decoder(); }
/** 中间一堆 .....*/ //源码中调用的方法... public static final <T> T parseObject(String text, Class<T> clazz) { //添加了一个Feature属性 return parseObject(text, clazz, new Feature[0]); } //进一步的封装 public static final <T> T parseObject(String text, Class<T> clazz, Feature[] features){ //添加解析的相关配置,解析的方式,以及Feature的额外属性信息 return parseObject(text, clazz, ParserConfig.getGlobalInstance(), DEFAULT_PARSER_FEATURE, features); } public static final <T> T parseObject(String input, Type clazz, ParserConfig config, int featureValues, Feature[] features){ //如果解析的数据为空,那么返回空. if (input == null) { return null; } //遍历Feature数组,将这些值进行叠加..最后生成一个FeatureValue. for (Feature featrue : features) { featureValues = Feature.config(featureValues, featrue, true); } //设置Json的解析方式.实例化DefaultExtJSONParser DefaultExtJSONParser parser = new DefaultExtJSONParser(input, config, featureValues); //真正的解析过程. Object value = parser.parseObject(clazz); //如果clazz != JSONArray的字节码文件..关闭解析 if (clazz != JSONArray.class) { parser.close(); } return value; }
}
以上就是将Json数据转化成Java Bean的源码调用过程.我们再来具体的分析一下..因为里面还是涉及到了很多的内容..我们可以看到在解析的时候并不是一步就直接完成的..中间过程需要封装相关的数据才能够对Json进行解析.首先就是Feature的封装.
Feature是一个枚举类型..它继承了Enum类..也就意味着这个枚举类型不能被继承,同样,枚举变量也是有限且固定的.并且创建一个枚举类型是线程安全的.更重要的一点就是枚举在进行序列化操作的时候能够保持单列模式..说白了就是在序列化和反序列化操作的时候保证操作的是同一个对象,也就不至于在序列化的时候针对的是当前这个对象,而在反序列化的时候需要重新new一个新的对象..
/* */ package com.alibaba.fastjson.parser; /* */ /* */ public enum Feature /* */ { /* 22 */ AutoCloseSource, /* */ /* 26 */ AllowComment, /* */ /* 30 */ AllowUnQuotedFieldNames, /* */ /* 34 */ AllowSingleQuotes, /* */ /* 38 */ InternFieldNames, /* */ /* 42 */ AllowISO8601DateFormat, /* */ /* 47 */ AllowArbitraryCommas, /* */ /* 52 */ UseBigDecimal, /* */ /* 57 */ IgnoreNotMatch, /* */ /* 62 */ SortFeidFastMatch, /* */ /* 67 */ DisableASM; /* */ /* */ private final int mask; /* */ //将枚举常量的序数左移一位. /* */ private Feature() /* */ { /* 74 */ this.mask = (1 << ordinal()); /* */ } /* */ //返回mask.. /* */ public final int getMask() /* */ { /* 80 */ return this.mask; /* */ } /* */ /* */ public static boolean isEnabled(int features, Feature feature) { /* 84 */ return (features & feature.getMask()) != 0; /* */ } /* */ //JSON源代码中调用的方法.将默认的属性与Feature的额外属性进行封装. /* */ public static int config(int features, Feature feature, boolean state) { /* 88 */ if (state) /* 89 */ features |= feature.getMask(); /* */ else { /* 91 */ features &= (feature.getMask() ^ 0xFFFFFFFF); /* */ } /* */ /* 94 */ return features; /* */ } /* */ }
这就是Feature的源代码..内部封装了一些枚举类型的变量..主要针对于Json的数据解析,Feature按照我自己的理解其实就是在解析Json或者是封装成Json时的特配置,我们可以看到在Json类中添加了Feature属性特征..最后将默认的一些特征与Feature的其他额外特征进行封装..最后进行解析或者打包..还要说一点就是还有一个是SerializerFeature属性..序列化属性,也是一个枚举类型..它其实也是针对Json的一些额外特征配置(比如说在Json中支持以双引号的形式将数据括起来等等)..
解析就是通过实例化DefaultExtJSONParser对象开始,然后通过调用相关的方法对Json进行解析..通过使用parseObject方法来完成解析..解析的过程也是Fast Json优化的最大的地方..也就导致了他的解析速度要超过其他api接口对Json数据解析的速度.
public <T> T parseObject(Type type){ if (this.lexer.token() == 8) { this.lexer.nextToken(); return null; } ObjectDeserializer derializer = this.config.getDeserializer(type); try{ return derializer.deserialze(this, type); }catch (JSONException e) { throw e; }catch (Throwable e) { throw new JSONException(e.getMessage(), e); }
}
这就是解析的源代码..其实看似非常的简单..实际上Fast Json在这个地方优化的地方非常的多,也就导致了其解析Json的速度是非常高效的.那么我们就来看看Fast Json为何比较高效。。
4.Fast Json高效的原因,为什么推荐使用Fast Json
Fast json高效自然而然是有它的原因的..我主要针对几点来说说..因为我并没有完全的看完Fast Json的源码..只是自己的一些见解,说的也就不是非常的全面..
1. 使用SerializeWriter
将数据封装成Json的时候使用SerializerWriter来完成,通过对字符串的追加将数据转换成Json字串.并且其内部提供了一些优化针对减少数组越界的判断.(减少了越界的判断确实能够提高一部分效率)
ThreadLocal来缓存buf,写入数据必然需要使用缓存来存放字符串的内存地址,但是每次序列化的时候都需要重新分配buf的内存空间,为了避免多次申请内存空间以及gc的调用,因此将内存保存到了ThreadLocal中,当再次进行序列化的时候只需要从ThreadLocal中取出内存就可以了.
并且ThreadLocal不仅仅可以缓存内存地址,当我们对Json数据进行解析的时候也会使用到它提供的缓存..当我们在解析Json的时候,我们读取到的key值可以被保存在一个叫sbuf的缓冲区中,只要这个缓冲区不被释放掉,那么再次读取的时候只需要从缓冲中读取到相关的字符串序列即可,没必要去new一个新的字符串对象,从而导致内存空间的开辟以及不必要的gc调用..
2.使用asm来避免反射机制的使用
asm不是一个算法,它是字节码框架技术,它可以避免使用Java中的反射机制从而获取到一个类中的所有成员变量和方法,反射机制同样也可以实现,但是反射机制的一大弊病就是耗费时间.使用反射中间会生成大量的临时变量从而导致gc的频繁调用.(避免了反射的使用,自然性能上也有了一定的优化) 至于asm技术我没有进行彻底的研究..可能以后会写..有兴趣的大家可以自己去研究研究..
3.使用IdentityHashMap去优化性能.
因为Fast Json每种类型都对应一种解析方式,也就出现了<class,JavaBeanSerializer>的映射关系,那么映射自然就需要使用有映射关系的集合,也就是HashMap<K,V>,但是HashMap并不是线程安全的,也就是在并发环境下会导致死循环的出现.因此在并发情况下我们就需要ConcurrentHashMap,但是比HashMap的性能要差,因为需要使用lock()等方法控制多线程的安全问题.安全就失去了效率,因此IndentityHashMap集合就在Fast Json中开发.他的内部去除了transfer方法,使得它能够在并发的情况下也能保证安全性.同样效率也就不会受到影响。
4.Deserializer的主要优化算法..
Deserializer也成为反序列化过程,也就是将Json数据转化成Java Bean形式.也是优化精力最多的地方.
基于token的预测分析:
解析Json 需要使用到词法处理,Fast Json使用了基于预测的词法分析方式,比如说:比如key之后,最大的可能是冒号":",value之后,可能是有两个,逗号","或者右括号"}" 这样的预测分析。
public void nextToken(int expect) { /* */ while (true) { /* 297 */ switch (expect) /* */ { /* */ case 12: /* 299 */ if (this.ch == '{') { /* 300 */ this.token = 12; /* 301 */ this.ch = this.buf[(++this.bp)]; /* 302 */ return; /* */ } /* 304 */ if (this.ch == '[') { /* 305 */ this.token = 14; /* 306 */ this.ch = this.buf[(++this.bp)]; /* 307 */ return; /* */ } /* */ case 16: /* 311 */ if (this.ch == ',') { /* 312 */ this.token = 16; /* 313 */ this.ch = this.buf[(++this.bp)]; /* 314 */ return; /* */ } /** 中间一堆....*/ if ((this.ch != ' ') && (this.ch != '\n') && (this.ch != '\r') && (this.ch != '\t') && (this.ch != '\f') && (this.ch != '\b')) break; /* 419 */ this.ch = this.buf[(++this.bp)]; /* */ } /* */ /* 423 */ nextToken(); /* */ }
预测分析的源代码就是上面粘贴的,只是粘贴了一部分,其实就是对下一个token的预测判断,这样可以帮助我们尽最快的速度拿到token..简单的说一下token的概念..
//这是一个Json字串..其中token单引号引起的部分每一个单引号引起的部分就是一个token这里一共有13个token.. { "id" : 123, "name" : "aa", "salary" : 56789} //json字串 '{' ' "id" ' ':' '123' ',' ' "name" ' ':' ' "aa" ' ',' ' "salary" ' ':' '56789' '}'
这样token的概念就没那么难理解了..
5.Sort field fast match算法
Fast Json在封装和解析的时候都是默认使用这个算法的,就是以有序的方式将Json 字符串进行保存,如果数据保存的形式是有序的,那么就使用优化算法,不用对每一个token进行处理..只需要处理一部分的token就可以了..也正是因为这个原因,使得Fast Json在解析Json的时候可以减少50%的token...这样效率上就有了空前的变化...如果不是有序的我们就需要一个一个进行分析了,然后从中取出key对应的value值.
就那上面那条Json数据来说吧,如果key值是有序的,那么只需要处理6个token就可以了,但是如果是无序的,就需要处理13个token..处理13个token不难理解..就是对扫描整个字符串,然后处理每一个token不就完事了..但是如果是有序的,那么为什么只需要处理6个token呢?我看了很多人的博客都没有对这块进行讲解..也就使得我也非常的迷茫..
我自己的理解是这样的:其实对不对我自己也不是非常的确定..(如果不对,大家请指出)
之所以只需要处理6个token就是只需要对key和value值进行处理,三对key,value对应的也是6个token,因为我们使用了预测分析,那么自然而然就知道了Json的数据格式,有了这个数据格式,那么那些符号类的token就可以直接跨越过去,我们已经匹配到了key那么自然就预测到key后面有一个","那么这个","我们就不需要处理了,只需要处理","后面的value了..因此符号类的token是没必要处理的,但是为什么要有序,因为我们是将每一条的Json数据转化成字符数组的形式,在匹配key值的时候我们是按照字符数组进行匹配,如果每一条的顺序都不一样,那么就不能使用一条规则去使者多条数据遵循这一个原则进行解析..因此才进行排序,可以减少对token的读取和处理..(个人理解..不对的话,请指出..)
这也就是Fast Json迅速的原因..自然有很多地方的东西没说到..Fast Json说是最快..是不是最快我们说了不算,但是Fast Json确实融入了很多的思想..还是很不错的一个解析Json的api接口..
至于用不用取决于大家..也没必要去争执谁解析Json最快..没什么意义..
原本其实想深入的研究一下,总结一下所有的原因和思想..但是看到很多人写的东西都是九牛一毛..抄来抄去的..没写出核心的东西..等自己真正有时间的时候再去解读一下源码再深入的写吧..
PS:最后只是吐槽一下..如果有人看着不爽..请不要喷我..谢谢!
最后说一下自己学这块的感悟:说实话之所以在Sort Field Fast match算法不是很明白的原因就是没有看到关于这块写的非常优秀的文章,看了挺多人的文章,看着是很厉害,但是写出来的东西大部分都是不清不楚,不明不白,我可以理解使用大量的专业术语(其实就是不说人话)..有的呢就是直接把人家写的东西直接粘贴过去,估计自己都没理解到底是怎么一回事..搬别人的东西不是不可以,只是在搬的时候我们也去想一想,最后结合我们自己的思想写到博客上,那才叫自己真正的学了,博文里才有自己的思想..直接抄来抄去,连改都不改动一下...真的很没意思..
感觉真的挺无力的,Sort这块纠结了很长时间到底要不要写出来,就怕误人子弟...很多博文写的东西只是写一个开头,就拿这个算法为什么只处理了6个token,没有什么过多的解释,什么是asm也没有什么过多的解释..(一开始还以为asm是算法,结果查了一下asm算法和图像处理有关,很明显就弄错了,后来才明白asm是字节码框架技术)..虽然我自己很菜..但是我能保证每一篇文章都是用很大的心思去写,自己心安就行...