alibaba fastjson(json序列化器)序列化部分源碼解析-2-性能優化B

 前面講了進行對象解析的兩個方面,並講了針對outWriter將不一樣類型的數據信息寫到buf字符數組。本篇講解對象解析的過程,即如何將不一樣類型的對象解析成outWriter所須要的序列信息。並考慮其中的性能優化。javascript

 取得解析器    
    首先咱們須要取得指定對象的json序列化器,以便使用特定的序列化器來序列化對象。所以,須要有一個方法來取得相對應的序列化器。在fastjson中,使用了一個相似map的結構來保存對象類型和及對應的解析器。對於對象類型,在整個fastjson中,分爲如下幾類:

    1    基本類型以及其包裝類型,字符串
    2    基本類型數組以及包裝類型數組
    3    Atomic類型
    4    JMX類型
    5    集合類型以及子類
    6    時間類型
    7    json類型
    8    對象數組類型
    9    javaBean類型java

    對於第1,2,3,4類型,在fastjson中使用了一個全局的單態實例來保存相對應的解析器;第5類型,處理集合類型,對於集合類型及其,因爲其處理邏輯均是同樣,因此只須要針對子類做一些的處理,讓其返回相對應的集合類型解析器便可;第6類型,時間處理器,將時間轉化爲相似yyyy-MM-ddTHH:mm:ss.SSS的格式;第7類型,處理fastjson專有jsonAwre類型;第8類型,處理對象的數組形式,即處理數組時,須要考慮數組中的統一對象類型;第9,即處理咱們最常使用的對象,javaBean類型,這也是在項目中解析得最多的類型。web

 

    咱們要看一下相對應的取解析器的方法,即類JsonSerializer.getObjectWriter(Class<?> clazz)方法,參考其中的實現:json

Java代碼    收藏代碼
  1. public ObjectSerializer getObjectWriter(Class<?> clazz) {  
  2.         ObjectSerializer writer = mapping.get(clazz);  
  3.         if (writer == null) {  
  4.             if (Map.class.isAssignableFrom(clazz)) {  
  5.                 mapping.put(clazz, MapSerializer.instance);  
  6.             } else if (List.class.isAssignableFrom(clazz)) {  
  7.                 mapping.put(clazz, ListSerializer.instance);  
  8.             } else if (Collection.class.isAssignableFrom(clazz)) {  
  9.                 mapping.put(clazz, CollectionSerializer.instance);  
  10.             } else if (Date.class.isAssignableFrom(clazz)) {  
  11.                 mapping.put(clazz, DateSerializer.instance);  
  12.             } else if (JSONAware.class.isAssignableFrom(clazz)) {  
  13.                 mapping.put(clazz, JSONAwareSerializer.instance);  
  14.             } else if (JSONStreamAware.class.isAssignableFrom(clazz)) {  
  15.                 mapping.put(clazz, JSONStreamAwareSerializer.instance);  
  16.             } else if (clazz.isEnum()) {  
  17.                 mapping.put(clazz, EnumSerializer.instance);  
  18.             } else if (clazz.isArray()) {  
  19.                 Class<?> componentType = clazz.getComponentType();  
  20.                 ObjectSerializer compObjectSerializer = getObjectWriter(componentType);  
  21.                 mapping.put(clazz, new ArraySerializer(compObjectSerializer));  
  22.             } else if (Throwable.class.isAssignableFrom(clazz)) {  
  23.                 mapping.put(clazz, new ExceptionSerializer(clazz));  
  24.             } else {  
  25.                 mapping.put(clazz, new JavaBeanSerializer(clazz));  
  26.             }  
  27.             writer = mapping.get(clazz);  
  28.         }  
  29.         return writer;  
  30.     }  

 

首先,取對象解析器是由一個類型爲JSONSerializerMap的對象mapping中取。之因此使用此類型,這是性能優化的一部分,此類型並非一個完整的map類型,只是實現了一個相似map的操做的類型。其內部並無使用基於equals的比較方式,而是使用了System.identityHashCode的實現。對於一個對象,其identityHashCode的值是定值,而對於一個類型,在整個jvm中只有一個,所以這裏使用基於class的identityHashCode是能夠了,也避免了使用class的euqlas來進行比較。
    接着再根據每個類型從mapping中取出相對應的解析器。首先從繼承而來的全局解析器取得解析器,若是對象不屬於第1,2,3,4類型,而開始進入如下的if else階段。
    咱們從上面的源碼中,能夠看出解析器主要分爲兩個部分,一個是與解析類型相關的,一個是無關的。好比對於第5,6,7類型,其中最5類型是集合類型,因爲不知道集合類型中的具體類型(由於存在繼承關係),因此類型無關。對於第8,9類型,其中第8類型爲對象數組類型,對於對象數組,數組中的每個對象的類型都是肯定的,且整個數組只有一種類型,所以能夠肯定其類型,這時候就要使用類型相關解析器了,對於第9類型,須要使用解析對象的類型來肯定相對應的javaBean屬性,所以是類型相關。
    另一個肯定解析器的過程中,使用了映射機制,即將當前解析器與對應的類型映射起來,以便下一次時使用。對於集合類型及子類型,因爲當前類型並非肯定的List或Collection類型,所以將當前類型與集合解析器映射起來。對於對象類型,須要將當前類型傳遞給相對應的解析器,以肯定具體的屬性。設計模式

    解析過程
    
    解析方法由統一的接口所定義,每一個不一樣的解析器只須要實際這個方法並提供各自的實現便可。此方法爲write(JSONSerializer serializer, Object object),由ObjectSerializer提供。帶兩個參數,第一個參數,即爲解析的起點類jsonSerializer,此類封裝了咱們所須要的outWriter類,須要時只須要今後類取出便可。第二個參數爲咱們所要解析的對象,在各個子類進行實現時,可將此對象經過強制類型轉換,轉換爲所須要的類型便可。
    具體的解析過程根據不一樣的數據類型不所不一樣,對於第1,2類型,因爲在outWriter中均有相對應的方法,因此在具體實現時,只須要調用相應的outWriter方法便可,而不須要做額外的工做。好比對於字符串解析器,它的解析過程以下所示:數組

Java代碼    收藏代碼
  1. SerializeWriter out = serializer.getWrier();  
  2.         String value = (String) object;  
  3.         if (serializer.isEnabled(SerializerFeature.UseSingleQuotes)) {  
  4.             out.writeStringWithSingleQuote(value);  
  5.         } else {  
  6.             out.writeStringWithDoubleQuote(value);  
  7.         }  

 

    即首先,取得輸出時所須要的outWriter,而後再根據配置決定是輸出單引號+字符串仍是雙引號+字符串。緩存

    而對於其它並無由outWriter所直接支持的類型而言,解析過程就相對於複雜一些。可是總的邏輯仍是基於如下邏輯:性能優化

  1.         基於數據類型特色輸出所特有的字符包裝內容
  2.         基於數據類型特色轉換爲outWriter所能識別的內容
  3.         逐步解析,將對象解析產生的字符數組輸出到outWriter中

    只需此三步,便可完美的解析一個對象。所以,咱們能夠參考其中的一個具體實現,並討論其中的具體優化措施。
    在取得解析器方法getObjectWriter(Class<?> clazz)中,咱們能夠看到,對於集合類型中的Collection和List,fastjson是分開進行解析的。即二者在解析時在細微處有着不同的實現。二者的區別在於List是有序的,能夠根據下標對元素進行訪問,對於經常使用List實現,ArrayList,使用下標訪問子元素的價格爲O1。這就是在fastJson中採起的一點優化措施,詳細看如下實現代碼:app

Java代碼    收藏代碼
  1. public final void write(JSONSerializer serializer, Object object) throws IOException {  
  2.         SerializeWriter out = serializer.getWrier();//取得輸出器  
  3.         List<?> list = (List<?>) object;//強制轉換爲所需類型  
  4.    
  5.         final int size = list.size();  
  6.         int end = size - 1;//此處定義爲size-1,是由於對最後一位有特殊處理  
  7. //空集合判斷,省略之  
  8.         out.append('[');//集合前綴包裝  
  9. /** 如下代碼使用get(X)方法訪問下標,實現代碼對於ArrayList實現有好處,對於LinkedList是否有好處,還待考慮 */  
  10.         for (int i = 0; i < end; ++i) {  
  11.             Object item = list.get(i);  
  12.             //空值判斷  
  13.                 Class<?> clazz = item.getClass();  
  14.                 if (clazz == Integer.class) {//針對Integer.class特殊優化,使用outWriter自帶方法  
  15.                     out.writeIntAndChar(((Integer) item).intValue(), ',');  
  16.                 } else if (clazz == Long.class) {//針對Long.class特殊優化,使用outWriter自帶方法  
  17.                     long val = ((Long) item).longValue();  
  18.                     out.writeLongAndChar(val, ',');  
  19.                 } else {  
  20.                     serializer.write(item);//遞歸調用,寫集合內元素  
  21.                     out.append(',');//間隔符  
  22.                 }  
  23.         }  
  24.    
  25. /** 如下代碼爲最後一位優化,當爲最後一位時,再也不須要輸出間隔符,而是輸出後綴包裝符 
  26. 這裏即在處理時,直接輸出後綴,與前面輸出間隔符相對應 */  
  27.         Object item = list.get(end);  
  28.             Class<?> clazz = item.getClass();  
  29.    
  30.             if (clazz == Integer.class) {  
  31.                 out.writeIntAndChar(((Integer) item).intValue(), ']');  
  32.             } else if (clazz == Long.class) {  
  33.                 out.writeLongAndChar(((Long) item).longValue(), ']');  
  34.             } else {  
  35.                 serializer.write(item);  
  36.                 out.append(']');  
  37.             }  
  38.     }  

 

    如下實現與collection相比不一樣的即在於處理中間元素與末尾元素的區別。相對於Collection,就不能使用以上的方法了,在實現上,就只能先輸出前綴,再一個一個地處理裏面的元素,最後輸出後綴。此實現以下所示:jvm

Java代碼    收藏代碼
  1. public void write(JSONSerializer serializer, Object object) throws IOException {  
  2.         SerializeWriter out = serializer.getWrier();  
  3.         Collection<?> collection = (Collection<?>) object;  
  4.         out.append('[');  
  5.         boolean first = true;  
  6.         for (Object item : collection) {  
  7.             if (!first) {out.append(',');}  
  8.             first = false;  
  9.    
  10.             Class<?> clazz = item.getClass();  
  11.             //Integer.class和Long.class特殊處理  
  12.             serializer.write(item);  
  13.         }  
  14.         out.append(']');  
  15.     }  

 

    以上代碼就是一般最多見的實現了。

    相對於集合類型實現,map實現和javaBean實現相對來講,稍微複雜了一點。主要是輸出key和value的問題。在fastjson中,key輸出表現爲使用outWriter的writeKey來進行輸出,value輸出則一樣使用常規的輸出。按照咱們先前所說的邏輯,首先仍是輸出包裝字符內容,即{,在末尾輸出}。而後,再根據每一個key-value映射特色,採起相對應的輸出方式。
    固然,對於map類型輸出和javaBean輸出仍是不同的。二者能夠互相轉換,但fastjson在輸出時仍是採起了不同的輸出方式。那麼,咱們以源代碼來查看相應的實現:

Java代碼    收藏代碼
  1. public void write(JSONSerializer serializer, Object object) throws IOException {  
  2.         SerializeWriter out = serializer.getWrier();  
  3.         Map<?, ?> map = (Map<?, ?>) object;  
  4.         out.write('{');//前綴  
  5.    
  6.         Class<?> preClazz = null;//緩存前一個value類型和相對應的解析器,減小類型判斷解析  
  7.         ObjectSerializer preWriter = null;  
  8.    
  9.         boolean first = true;  
  10.         for (Map.Entry<?, ?> entry : map.entrySet()) {  
  11. //此處有刪除,即根據nameFilter和valueFilter針對key-value做轉換處理  
  12.             if (!first) {out.write(',');}//輸出間隔符  
  13.    
  14.             serializer.writeFieldName(key);//輸出字段名+冒號  
  15.             first = false;  
  16.    
  17.             Class<?> clazz = value.getClass();  
  18.             if (clazz == preClazz) {//此處即細節優化內容,直接使用前一個解析器,避免再次從jsonSerializer中查找  
  19.                 preWriter.write(serializer, value);  
  20.             } else {  
  21. /** 此處則就須要從jsonSerializer中查找解析器,並輸出了 */  
  22.                 preClazz = clazz;  
  23.                 preWriter = serializer.getObjectWriter(clazz);  
  24.                 preWriter.write(serializer, value);  
  25.             }  
  26.         }  
  27.         out.write('}');//後綴  
  28.     }  

 

    由上能夠看出,map的實現仍是按照咱們經常使用的思路在進行。在性能優化處,採起了兩個優化點,一是輸出字段時,並不直接輸出字段名,而是採起字段+冒號的方式一塊兒輸出,減小outWriter擴容計算。二是在查找value解析器時,儘可能使用前一個value的解析器,避免重複查找。
    相比map,javaBean的實現就相對更復雜。javaBean輸出並非採起key-value的方式,而是採起相似fieldSerializer的輸出方式,即將屬性名和值,組合成一個字段,一塊兒進行輸出。固然輸出時仍是先輸出字段名,再輸出值的實現。那麼對於javaBean實現,首先要取得當前對象類型的全部能夠輸出的類型。
    在fastjson實現中,並無採起javaBean屬性的讀取方式,而是採起了使用getXXX和isXXX方法的讀取模式,來取得一個類型的可讀取屬性。做爲性能優化的一部分,讀取屬性的操做結果被緩存到了getter緩存器中。其實,並非緩存到了getter緩存器中,只是該類型的javaBean序列化器對象被緩存到了jsonSerializer的對象類型-序列化器映射中。對於同一個類型,就不須要再次解析該類型的屬性了。
    有了相對應的字段,那麼在實現時,就按照相應的字段進行輸出便可。如下爲實現代碼:

Java代碼    收藏代碼
  1. public void write(JSONSerializer serializer, Object object) throws IOException {  
  2.         SerializeWriter out = serializer.getWrier();  
  3.             out.append('{');//前綴  
  4.    
  5.             for (int i = 0; i < getters.length; ++i) {  
  6.                 FieldSerializer getter = getters[i];//取屬性解析器  
  7.                 Object propertyValue = getter.getPropertyValue(object);//取值  
  8. //省略中間nameFilter和valueFilter過濾處理  
  9.                 if (commaFlag) {out.append(',');}//間隔符  
  10. //省略nameFilter和valueFilter過濾以後的輸出處理  
  11.                getter.writeProperty(serializer, propertyValue);//使用字段解析器輸出內容  
  12.             }  
  13.             out.append('}');//後綴  
  14.     }  

 

    由上可見,javaBean的輸出實際上和map輸出差很少。只不過這裏又把屬性的解析和輸出封裝了一層。在使用字段解析器(由FieldSerializer標識)輸出字段值時,實際上也是先輸出字段名+冒號,再輸出字段值。這裏就再也不詳細敘述。

    總結

    在整個解析過程當中,更多的是根據對象類型查找到對象解析器,再使用對象解析器序列化對象的過程。在這中間,根據不一樣的對象採起不一樣的解析,並在實現中採起部分優化措施,以儘可能地提升解析效率,減小中間運算。減小中間運算,是在解析過程當中採起的最主要的優化辦法。實際上,最主要的優化措施仍是體如今outWriter中對於數據的處理上。此處更多的是設計模式的使用,以實現繁多的對象的解析。     整個fastjson的序列化部分,就到此爲止。單就筆者而言,在查看源代碼的時候,也發現了一些問題,多是做者未考慮的問題,或者是實際中未遇到。但在版本升級過程當中,也漸漸地對功能進行了加強,好比對於@JsonField註解的使用,NameFilter和ValueFilter的使用,使fastjson愈來愈符合業務系統的須要。若是能夠,筆者會將其用到筆者所在的項目中,而再也不重複發明輪子:)

相關文章
相關標籤/搜索