接上篇,在論述完基本概念和整體思路以後,咱們來到整個程序最重要的部分-性能優化。之因此會有fastjson這個項目,主要問題是爲了解決性能這一塊的問題,將序列化工做提升到一個新的高度。咱們提到,性能優化主要有兩個方面,一個如何將處理後的數據追加到數據儲存器,即outWriter中;二是如何保證處理過程當中的速度。
本篇從第一個性能優化方面來進行解析,主要的工做集中在類SerializeWriter上。javascript
首先,類的聲明,繼承了Writer類,實現了輸出字符的基本功能,而且提供了拼接數據的基本功能。內部使用了一個buf數組和count來進行計數。這個類的實現結果和StringBuilder的工做模式差很少。但咱們說爲何不使用StringBuilder,主要是由於StringBuilder沒有針對json序列化提出更加有效率的處理方式,並且單就StringBuilder而言,內部是爲了實現字符串拼接而生,由於很天然地使用了更加可以讀懂的方式進行處理。相比,serializeWriter單處理json序列化數據傳輸,功能單一,所以在某些方面更加優化一些。
在類聲明中,這裏有一個優化措施(筆者最開始未注意到,經做者指出以後才明白)。便是對buf數組的緩存使用,即在一次處理完畢以後,儲存的數據容器並不銷燬,而是留在當前線程變量中。以便於在當前線程中再次序列化json時使用。源碼以下:java
在初始構造時,會從當前線程變量中取buf數組並設置在對象屬性buf中。而在每次序列化完成以後,會經過close方法,將此buf數組再次綁定在線程變量當中,以下所示:web
固然,buf從新綁定了,確定計數器count應該置0。這是天然,count是對象屬性,每次在新建時,天然會置0。json
在實現過程中,不少具體的實現是借鑑了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,具體能夠見相應函數或StringBuilder.ensureCapacityImpl函數。性能優化
實現解析app
基本功能
基本功能有如下幾個函數:函數
其中第一個函數,能夠忽略,能夠理解爲實現writer中的writ(int)方法,在具體應用時未用到此方法。第2個方法和第7個方法爲寫單個字符,即往buf數組中寫字符。第3,4,5,6,均是寫一個字符數組(字符串也能夠理解爲字符數組)。所以,咱們單就字符數組進行分析,源碼以下:
從上註釋能夠看出,其處理流程和咱們所說的標準處理邏輯一致。在處理字符拼接時,儘可能使用最快的方法,如使用System.arrayCopy和字符串中的getChars方法。另外幾個方法處理邏輯與此方法相同。
警告:不要在正式應用中對有存在特殊字符的字符串(無特殊字符的字符串除外)使用以上的輸出方式,請使用第5組方式進行json輸出。對於字符數組的處理在以上處理方式中不會對特殊字符進行處理。如字符串 3\"'4,在使用以上方式輸出時,只會輸出 3"'4,其中的轉義字符在轉化爲toChar時被刪除掉。
所以,在實際處理中,只有字符數組會使用以上方式進行輸出。不要將字符串與字符數組相混合。字符數組不考慮轉義問題,而字符串須要考慮轉義。
整形和長整形
方法以下:
這兩個方法,按照咱們的邏輯,首先須要將整性和長整性轉化爲字符串(無特殊字符),而後以字符數組的形式輸出便可。在進行處理時,主要參考了Integer和Long的toString實現方式和長度計算。首先看一個實現:
以上首先看特殊數字的處理,由於int的範圍從-2147483648到2147483647,所以對於-2147483648這個特殊數字(不能轉化爲-號+正數的形式),進行特殊處理。這裏調用了write(str)方法,實際上就是調用了在第一部分的public void write(String str, int off, int len),這裏是安全的,由於沒有特殊字符。
其次是計算長度,二者都借鑑了jdk中的實現,分別爲Integer.stringSize和Long.stringSize,這裏就再也不敘述。
再寫入buf數組,咱們說都是將數字轉化爲字符數組,再定入buf數組中。這裏的實現,即按照這個步驟在進行。這裏在IOUtils中,借鑑了Integer.getChars(int i, int index, char[] buf)方法和Long.getChars(long i, int index, char[] buf)方法,這裏也再也不敘述。
基本類型數組
數組的形式,主要是將數組的每一部分輸出出來,便可。在輸出時,須要輸出前綴「[」和後綴「]」以及每一個數據之間的「,「。按照咱們的邏輯,首先仍是計算長度,其次是準備空間,再者是寫數據,最後是定count值。所以,咱們參考一個實現:
此處有關於性能優化的地方,主要有幾個地方。首先將minValue和普通數字分開計算,以免可能出現的問題;在計算長度時,儘可能調用前面使用stringToSize方法,此方法最快;在進行字符追加時,利用getChars方法進行處理。
對於仍有優化的地方,好比對於boolArray,在處理時,又有了特殊優化,主要仍是在上面的兩點,計算長度時,儘可能地快,以及在字符追加時也儘可能的快。如下爲對於boolean數據的兩個優化點:
數字+字符輸出
以上兩個方法主要在處理如下狀況下使用,在不知道要進行序列化的對象的長度的狀況下,要儘可能避免進行buf數據擴容的狀況出現。儘管這種狀況不多發生,但仍是儘可能避免。特殊是在輸出集合數據的狀況下,在集合數據輸出下,各個數據的長度未定,所以不能計算出總輸出長度,只能一個對象一個對象輸出,在這種狀況下,先要輸出一個對象,而後再輸出對象的間隔符或結尾符。若是先調用輸出數據,再調用輸出間隔符或結尾符,遠不如將二者結合起來,一塊兒進行計算和輸出。
此方法基於如下一個事實:儘可能在已知數據長度的狀況下進行字符拼接,這樣有利於快速的爲數據準備數據空間。
在具體實現時,此方法只是減小了數據擴容的計算,其它方法與基本實現和組合是一致的,以writeIntAndChar爲例:
字符串處理
做爲在業務系統中最經常使用的類型,字符串是一個必不可少的元素之一。在json中,字符串是以雙(單)引號,引發來使用的。所以在輸出時,即要在最終的數據上追加雙(單)引號。不然,js會將其做爲變量使用而報錯。並且在最新的json標準中,對於json中的key,也要求必須追加雙(單)引號以示區分了。字符串處理方法有如下幾種:
其中第1,2方法表示分別用雙引號和單引號將字符串包裝起來,第3,4方法表示在字符串輸出完畢以後,再輸出一個冒號,第5方法表示輸出一個字符串數組,使用雙引號包裝字符串。第7,8方法未知(不明真相的方法?)
字符串是能夠知道長度的,因此第一步肯定長度即OK了。 在第一步擴容計算以後,須要處理一個在字符串中特殊的問題,即轉義字符處理。如何處理轉義字符,以及避免沒必要要的擴容計算,是必需要考慮的。在fastjson中,採起了首先將其認定爲全非特殊字符,而後再一個個字符判斷,對特殊字符再做處理的方法。在必定程序上避免了在一個個判斷時,擴容計算的問題。咱們就其中一個示例進行分析:
在處理字符串上,特殊的即在特殊字符上。由於在輸出時,要輸出時要保存字符串的原始模式,如\"的格式,要輸出時,要輸出爲\ + "的形式,而不能直接輸出爲\",後者在輸出時就直接輸出爲",而省略了\,這在js端是會報錯的。
總結:
在針對輸出優化時,主要利用了最有效率的手段進行處理。如針對數字和boolean時的處理方式。同時,在處理字符串時,也採起了先處理最經常使用字符,再處理特殊字符的形式。在針對某些常常碰到的場景時,使用了聯合處理的手段(如writeIntAndChar),而再也不是分開處理。
整個處理的思想,便是在處理單個數據時,採起最優方式;在處理複合數據時,避免擴容計算;儘可能使用jdk中的方法,以免重複輪子(可能輪子更慢)。
下一篇,從數據處理過程對源碼進行分析,同時解析其中針對性能優化的處理部分。