靜行:FastJSON實現詳解

allowtransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?url=http%3A%2F%2Fwww.csdn.net%2Farticle%2F2014-09-25%2F2821866&type=3&count=&appkey=&title=%E2%80%9C%E5%BF%AB%E2%80%9D%E4%BD%9C%E4%B8%BA%E7%A8%8B%E5%BA%8F%E5%91%98%E8%BF%BD%E9%80%90%E7%9A%84%E7%BB%88%E6%9E%81%E7%9B%AE%E6%A0%87%E4%B9%8B%E4%B8%80%EF%BC%8C%E8%80%8CFastJSON%E5%88%99%E5%BE%88%E5%A5%BD%E7%9A%84%E8%AF%81%E6%98%8E%E4%BA%86%E8%BF%99%E4%B8%80%E7%89%B9%E6%80%A7%E3%80%82%E6%9C%AC%E6%9C%9F%E3%80%8A%E9%97%AE%E5%BA%95%E3%80%8B%EF%BC%8C%E9%9D%99%E8%A1%8C%E5%B0%86%E5%B8%A6%E5%A4%A7%E5%AE%B6%E8%A7%81%E8%AF%81%E5%AE%83%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%9A%84%E5%AE%9E%E7%8E%B0%E8%BF%87%E7%A8%8B%EF%BC%8C%E4%B8%80%E8%B5%B7%E9%A2%86%E7%95%A5%E5%AE%83%E7%9A%84%E2%80%9C%E5%BF%AB%E2%80%9D%E6%84%9F%E3%80%82&pic=&ralateUid=&language=zh_cn&rnd=1430372220031" width="22" height="16">摘要:“快”作爲程序員追逐的終極目標之一,而FastJSON則很好的證明了這一特性。本期《問底》,靜行將帶大家見證它序列化和反序列化的實現過程,一起領略它的“快”感。

還記得電影《功夫》中火雲邪神的一句話:天下功夫,無堅不破,唯快不破。在程序員的世界中,“快”一直是大家苦苦修煉,競相追逐的終極目標之一,甚至到了“不擇手段”、“錙銖必較”的地步。

一直使用json遊離於各種編程語言和系統之間。一個偶然的機會碰到了Fastjson,被他的無依賴、易使用、應用廣等特性深深吸引的同時,更被他出奇的“快”所震驚,在java界猶如一騎絕塵,旁人只能望其項背。很自然的一個想法湧上心頭:FastJSON爲何如此之快?於是定神來拔一拔其實現,一則膜拜大師的傑作,二則虛心偷技,三則方便來者學習。

本篇接下來的內容是基於FastJSON 1.1.40,着重講述其序列化、反序列化實現,最後分析FastJSON爲何如此“fast”的原因。

1. 序列化

所謂序列化,就是將java各種對象轉化爲json串。不多說,先上圖。

 

 序列化入口

平常我們經常用到的是JSON.toJSONString()這個靜態方法來實現序列化。其實JSON是一個抽象類,該類實現了JSONAware(轉爲json串)和JSONStreamAware(將json串寫入Appendable中)的接口,同時又是JSONArray(內部實現就是個List)和JSONObject(內部實現就是個Map)的父類。JSON.toJSONString()方法內部實現基本相同,爲做某些特定配置,對外暴露的接口可能不同。該方法的實現實際託付給了JSONSerializer類。

序列化組合器

JSONSerializer類相當於一個序列化組合器,它將上層調用、序列化配置、具體類型序列化實現、序列化字符串拼接等功能組合在一起,方便外部統一調用。該類有幾個重要的成員,SerializeConfig、SerializeWriter、各種Filter列表、DateFormat、SerialContext等,還有每次對各個具體對象序列化的ObjectSerializer(非JSONSerializer的成員變量)。下面就來挨個說明其各自功能。

1.  SerializeConfig

SerializeConfig是全局唯一的,它繼承自IdentityHashMap,IdentityHashMap是一個長度默認爲1024的Hash桶,每個桶存放相同Hash的Entry(可看做鏈表節點,包含key、value、next指針、hash值)做成的單向鏈表,IdentityHashMap實現了HashMap的功能,但能避免HashMap併發時的死循環。  

SerializeConfig的主要功能是配置並記錄每種Java類型對應的序列化類(ObjectSerializer接口的實現類),比如Boolean.class使用BooleanCodec(看命名就知道該類將序列化和反序列化實現寫到一起了)作爲序列化實現類,float[].class使用FloatArraySerializer作爲序列化實現類。這些序列化實現類,有的是FastJSON中默認實現的(比如Java基本類),有的是通過ASM框架生成的(比如用戶自定義類),有的甚至是用戶自定義的序列化類(比如Date類型框架默認實現是轉爲毫秒,應用需要轉爲秒)。當然,這就涉及到是使用ASM生成序列化類還是使用JavaBean的序列化類類序列化的問題,這裏判斷根據就是是否Android環境(環境變量"java.vm.name"爲"dalvik"或"lemur"就是Android環境),但判斷不僅這裏一處,後續還有更具體的判斷。

2.  SerializeWriter

SerializeWriter繼承自Java的Writer,其實就是個轉爲FastJSON而生的StringBuilder,完成高性能的字符串拼接。該類成員如下:

  • char buf[]
 可理解爲每次序列化後字符串的內存存放地址。

  • static ThreadLocal> bufLocal 
每次序列化,都需要重新分配buf[]內存空間。而bufLocal就是每次序列化後bug[]的內存空間保留到ThreadLocal裏,但其中的值清空,避免頻繁的內存分配和gc。

  • int features 
生成json字符串的特徵配置,默認配置爲: 
<span>QuoteFieldNames | SkipTransientField | WriteEnumUsingToString | SortField</span>

 表示含義爲:雙引號filedName and 忽略transientField and enum類型使用String寫入 and 排序輸出field。 支持的所有特徵在SerializerFeature類中,用戶可在調用時顯示配置,也可通過JSONFiled或JSONType注入配置。

  • Writer 
writer 用戶指定將生成的json串直接寫入某writer中,比如JSONWriter類。

舉個例子吧,writeStringWithDoubleQuote()表示用字符串用雙引號寫入,看看如何拼接字符串的。

3.  Filter列表

SerializeWriter中有很多Filter列表,可視爲在生成json串的各階段、各地方定製序列化,大致如下:

  • BeforeFilter :序列化時在最前面添加內容
  • AfterFilter :序列化時在最後面添加內容
  • PropertyFilter :根據PropertyName和PropertyValue來判斷是否序列化
  • ValueFilter :修改Value
  • NameFilter :修改key
  • PropertyPreFilter :根據PropertyName判斷是否序列化

4.  DateFormat

指定日期格式。若不指定,FastJSON會自動識別如下日期格式:

  • ISO-8601日期格式
  • yyyy-MM-dd
  • yyyy-MM-dd HH:mm:ss
  • yyyy-MM-dd HH:mm:ss.SSS
  • 毫秒數值
  • 毫秒字符串
  • .Net Json日期格式
  • new Date()

5.  SerialContext

序列化上下文,在引用或循環引用中使用,該值會放入references的Hash桶(IdentityHashMap)緩存。

6.  ObjectSerializer  

ObjectSerializer只有一個接口方法,如下:

void write(JSONSerializer serializer,Objectobject,Object
    fieldName,Type fieldType);

可見,將JSONSerializer傳入了ObjectSerializer中,而JSONSerializer有SerializeWriter成員,在每個具體ObjectSerializer實現中,直接使用SerializeWriter拼接字符串即可;Object即是待序列化的對象;fieldName則主要用於組合類引用時設置序列化上下文;而fieldType主要是爲了泛型處理。  

JSONSerializer中通過public ObjectSerializer getObjectWriter(Class clazz)函數獲取類對應的序列化類(即實現ObjectSerializer接口的類),大致邏輯如下:

 

整個過程是先獲取已實現基礎類對應的序列化類,再通過類加載器獲取自定義的AutowiredObjectSerializer序列化類,最後獲取通過createJavaBeanSerializer()創建的序列化類。通過該方法會獲取兩種序列化類,一種是直接的JavaBeanSerializer(根據類的get方法、public filed等JavaBean特徵序列化),另一種是createASMSerializer(通過ASM框架生成的序列化字節碼),優先使用第二種。選擇JavaBeanSerializer的條件爲:

  • 該clazz爲非public類
  • 該clazz的類加載器在ASMClassLoader的外部,或者clazz就是 Serializable.class,或者clazz就是Object.class
  • JSONType的註解指明不適用ASM
  • createASMSerializer加載失敗 

結合前面的討論,可以得出使用ASM的條件:非Android系統、非基礎類、非自定義的AutowiredObjectSerializer、非以上所列的使用JavaBeanSerializer條件。 

具體基礎類的序列化方法、JavaBeanSerializer的序列化方法和ASM生成的序列化方法可以參見代碼,這裏就不做一一講解了。

2. 反序列化

所謂反序列化,就是將json串轉化爲對應的java對象。還是先上圖。

 

同樣是JSON類作爲反序列化入口,實現了parse()、parseObject()、parseArray()等將json串轉換爲java對象的靜態方法。這些方法的實現,實際託付給了DefaultJSONParser類。   

DefaultJSONParser類相當於序列化的JSONSerializer類,是個功能組合器,它將上層調用、反序列化配置、反序列化實現、詞法解析等功能組合在一起,相當於設計模式中的外觀模式,供外部統一調用。同樣,我們來分析該類的幾個重要成員,看看他是如何實現紛繁的反序列化功能的。

1.  ParserConfig

同SerializeConfig,該類也是全局唯一的解析配置,其中的boolean asmEnable同樣判斷是否爲Andriod環境。與SerializeConfig不同的是,配置類和對應反序列類的IdentityHashMap是該類的私有成員,構造函數的時候就將基礎反序列化類加載進入IdentityHashMap中。

2.  JSONLexer 

JSONLexer是個接口類,定義了各種當前狀態和操作接口。JSONLexerBase是對JSONLexer實現的抽象類,類似於序列化的SerializeWriter類,專門解析json字符串,並做了很多優化。實際使用的是JSONLexerBase的兩個子類JSONScanner和JSONLexerBase,前者是對整個字符串的反序列化,後者是接Reader直接序列化。簡析JSONLexerBase的某些成員:

  • int token

由於json串具有一定格式,字符串會根據某些特定的字符來自解釋所表示的意義,那麼這些特定的字符或所處位置的字符在FastJSON中就叫一個token,比如"(","{","[",",",":",key,value等,這些都定義在JSONToken類中。

  • char[] sbuf

解析器通過掃描輸入字符串,將匹配得到的最細粒度的key、value會放到sbuf中。

  • static ThreadLocal> SBUF_REF_LOCAL

上面sbuf的空間不釋放,在下次需要的時候直接拿出來使用,從避免的內存的頻繁分配和gc。

  • features

反序列化特性的配置,同序列化的feature是通過int的位或來實現其特性開啓還是關閉的。默認配置是: AutoCloseSource | UseBigDecimal | AllowUnQuotedFieldNames | AllowSingleQuotes | AllowArbitraryCommas | AllowArbitraryCommas | SortFeidFastMatch | IgnoreNotMatch ,表示檢查json串的完整性 and 轉換數值使用BigDecimal and 允許接受不使用引號的filedName and 允許接受使用單引號的key和value and 允許接受連續多個","的json串 and 使用排序後的field做快速匹配 and 忽略不匹配的key/value對。當然,這些參數也是可以通過其他途徑配置的。

  • hasSpecial

對轉義符的處理,比如'\0','\'等。

詞法解析器是基於預測的算法從左到右一次遍歷的。由於json串具有自身的特點,比如爲key的token後最有可能是":",":"之後可能是value的token或爲"{"的token或爲"["的token等等,從而可以根據前一個token預判下一個token的可能,進而得知每個token的含義。分辨出各個token後,就可以獲取具體值了,比如scanString獲取key值,scanFieldString根據fieldName獲取fieldValue,scanTrue獲取java的true等等。其中,一般會對key進行緩存,放入SymbolTable(類似於IdentityHashMap)中,猜想這樣做的目的是:應用解析的json串一般key就那麼多,每次生成開銷太多,乾脆緩存着,用的就是就來取,還是空間換時間的技巧。

3.  List< ExtraTypeProvider >和List< ExtraProcessor >

視爲對其他類型的處理和其他自定義處理而留的口子,用戶可以自己實現對應接口即可。

4.  DateFormat

同序列化的DateFormat,不多說了。

5.  ParseContext 和 List< ResolveTask >

ParseContext同序列化的SerialContext,爲引用甚至循環引用做準備。   

List< ResolveTask >當然就是處理這種多層次甚至多重引用記錄的list了。

6.  SymbolTable

上面提到的key緩存。

7.  ObjectDeserializer

跟ObjectSerializer也是相似的。先根據fieldType獲取已緩存的解析器,如果沒有則根據fieldClass獲取已緩存的解析器,否則根據註解的JSONType獲取解析器,否則通過當前線程加載器加載的AutowiredObjectDeserializer查找解析器,否則判斷是否爲幾種常用泛型(比如Collection、Map等),最後通過createJavaBeanDeserializer來創建對應的解析器。當然,這裏又分爲JavaBeanDeserializer和asmFactory.createJavaBeanDeserializer兩種。使用asm的條件如下:

  • 非Android系統
  • 該類及其除Object之外的所有父類爲是public的
  • 泛型參數非空
  • 非asmFactory加載器之外的加載器加載的類
  • 非接口類
  • 類的setter函數不大於200
  • 類有默認構造函數
  • 類不能含有僅有getter的filed
  • 類不能含有非public的field
  • 類不能含有非靜態的成員類
  • 類本身不是非靜態的成員類

使用ASM生成的反序列化器具有較高的反序列化性能,比如對排序的json串可按順序匹配解析,從而減少讀取的token數,但如上要求也是蠻嚴格的。綜上,FastJSON反序列化也支持基礎反序列化器、JavaBeanDeserializer反序列化器和ASM構造的反序列化器,這裏也不做一一講解了。

3. Why So Fast

FastJSON真的很快,讀後受益匪淺。個人總結了下快的原因(不一定完整):

1.  專業的心做專業的事

不論是序列化還是反序列化,FastJSON針對每種類型都有與之對應的序列化和反序列化方法,就針對這種類型來做,優化性能自然更具針對性。自編符合json的SerializeWriter和JSONLexer,就連ASM框架也給簡化掉了,只保留所需部分。不得不嘆其用心良苦。

2.  無處不在的緩存

空間換時間的想法爲程序員屢試不爽,而作者將該方法用到任何細微之處:類對應的序列化器/反序列化器全部存起來,方便取用;解析的key存起來,表面重複內存分配等等。

3.  不厭其煩的重複代碼

我不知道是否作者故意爲之,程序中出現了很多類似的代碼,比如特殊字符處理、不同函數對相同token的處理等。這樣雖對於程序員尋求規整相違背,不過二進制代碼卻很喜歡,無形之中減少了許多函數調用。

4.  不走尋常路

對於JavaBean,可以通過發射實現序列化和反序列化(FastJSON已有實現),但默認使用的是ASM框架生成對應字節碼。爲了性能,無所不用其極。

5.  一點點改變有很大的差別

排序對輸出僅是一點小小的改變,絲毫不影響json的使用,但卻被作者用在瞭解析的快速匹配上,而不用挨個拎出key。

6.  從規律中找性能

上面也講到,FastJSON讀取token基於預測的。json串自身的規律性被作者逮個正着,預測下一個將出現的token處理比迷迷糊糊拿到一個token再分情況處理更快捷。

結束語

不喜歡虎頭蛇尾的結局。不過寫到這裏,除了承認自己對FastJSON代碼某些地方還沒看懂或理解有偏頗之外,不敢說太多了。

關於作者:阿里巴巴集團CDO數據開發平臺高級工程師,2010年加入阿里巴巴,長期從事各存儲系統(mysql、oracle、odps、hadoop、sqlserver、rds、drds、hbase、oceanbase、db2、ots、tair等)間實時和離線的數據同步工作,打造阿里巴巴雲上雲下的數據同步通道。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章