為什麼Fastjson能夠做到這麼快

一、FastJson介紹

在日常的java項目開發中,JSON的使用越來越頻繁,對於Json的處理工具也有很多。接下來就介紹一下阿里開源的一個高性能的JSON框架FastJson,功能完善,完全支持標準JSON庫,現在已經越來越受到開發者的青睞。

二、 FastJson的特點:

1) FastJson數據處理速度快,無論序列化(把JavaBean對象轉化成Json格式的字符串)和反序列化(把JSON格式的字符串轉化為Java Bean對象),都是當之無愧的fast

2) 功能強大(支持普通JDK類,包括javaBean, Collection, Date 或者enum)

3) 零依賴(沒有依賴其他的任何類庫)

三、 FastJson的簡單說明:

FastJson對於JSON格式的字符串的解析主要是用到了下面三個類:

1) JSON:FastJson的解析器,用於JSON格式字符串與JSON對象及JavaBean之間的轉化。也是最基礎的一個類,因為看過源碼之後會發現,下面的兩個類繼承了JSON類,其中很多方法的實現也是基於JSON類中的parse()方法。

2) JSONObject:FastJson提供的json對象,用於將String對象、javaBean、Collection等解析為JSON格式的對象

3) JSONArray:FastJson提供json數組對象

為什麼Fastjson能夠做到這麼快

四、為什麼Fastjson能夠做到這麼快?

Fastjson中Serialzie的優化實現

1、自行編寫類似StringBuilder的工具類SerializeWriter。

把java對象序列化成json文本,是不可能使用字符串直接拼接的,因為這樣性能很差。比字符串拼接更好的辦法是使用java.lang.StringBuilder。StringBuilder雖然速度很好了,但還能夠進一步提升性能的,fastjson中提供了一個類似StringBuilder的類com.alibaba.fastjson.serializer.SerializeWriter。

SerializeWriter提供一些針對性的方法減少數組越界檢查。例如public void writeIntAndChar(int i, char c) {},這樣的方法一次性把兩個值寫到buf中去,能夠減少一次越界檢查。目前SerializeWriter還有一些關鍵的方法能夠減少越界檢查的,我還沒實現。也就是說,如果實現了,能夠進一步提升serialize的性能。

2、使用ThreadLocal來緩存buf。

這個辦法能夠減少對象分配和gc,從而提升性能。SerializeWriter中包含了一個char[] buf,每序列化一次,都要做一次分配,使用ThreadLocal優化,能夠提升性能。

3、使用asm避免反射

獲取java bean的屬性值,需要調用反射,fastjson引入了asm的來避免反射導致的開銷。fastjson內置的asm是基於objectweb asm 3.3.1改造的,只保留必要的部分,fastjson asm部分不到1000行代碼,引入了asm的同時不導致大小變大太多。

使用一個特殊的IdentityHashMap優化性能。

fastjson對每種類型使用一種serializer,於是就存在class -> JavaBeanSerizlier的映射。fastjson使用IdentityHashMap而不是HashMap,避免equals操作。我們知道HashMap的算法的transfer操作,併發時可能導致死循環,但是ConcurrentHashMap比HashMap系列會慢,因為其使用volatile和lock。fastjson自己實現了一個特別的IdentityHashMap,去掉transfer操作的IdentityHashMap,能夠在併發時工作,但是不會導致死循環。

5、缺省啟用sort field輸出

json的object是一種key/value結構,正常的hashmap是無序的,fastjson缺省是排序輸出的,這是為deserialize優化做準備。

6、集成jdk實現的一些優化算法

在優化fastjson的過程中,參考了jdk內部實現的算法,比如int to char[]算法等等。

為什麼Fastjson能夠做到這麼快

五、fastjson的deserializer的主要優化算法

deserializer也稱為parser或者decoder,fastjson在這方面投入的優化精力最多。

1、讀取token基於預測。

所有的parser基本上都需要做詞法處理,json也不例外。fastjson詞法處理的時候,使用了基於預測的優化算法。比如key之後,最大的可能是冒號":",value之後,可能是有兩個,逗號","或者右括號"}"。在com.alibaba.fastjson.parser.JSONScanner中提供了這樣的方法:

Java代碼 :

public void nextToken(int expect) { 
for (;;) {
switch (expect) {
case JSONToken.COMMA: //
if (ch == ',') {
token = JSONToken.COMMA;
ch = buf[++bp];
return;
}
if (ch == '}') {
token = JSONToken.RBRACE;
ch = buf[++bp];
return;
}
if (ch == ']') {
token = JSONToken.RBRACKET;
ch = buf[++bp];
return;
}
if (ch == EOI) {
token = JSONToken.EOF;
return;
}
break;
// ... ...
}

}


從上面代碼看,基於預測能夠做更少的處理就能夠讀取到token。

2、sort field fast match算法

fastjson的serialize是按照key的順序進行的,於是fastjson做deserializer時候,採用一種優化算法,就是假設key/value的內容是有序的,讀取的時候只需要做key的匹配,而不需要把key從輸入中讀取出來。通過這個優化,使得fastjson在處理json文本的時候,少讀取超過50%的token,這個是一個十分關鍵的優化算法。基於這個算法,使用asm實現,性能提升十分明顯,超過300%的性能提升。

Java代碼

{ "id" : 123, "name" : "小黑", "salary" : 56789.79} 
------ -------- ----------

在上面例子看,虛線標註的三個部分是key,如果key_id、key_name、key_salary這三個key是順序的,就可以做優化處理,這三個key不需要被讀取出來,只需要比較就可以了。

這種算法分兩種模式,一種是快速模式,一種是常規模式。快速模式是假定key是順序的,能快速處理,如果發現不能夠快速處理,則退回常規模式。保證性能的同時,不會影響功能。

在這個例子中,常規模式需要處理13個token,快速模式只需要處理6個token。

實現sort field fast match算法的代碼在這個類[com.alibaba.fastjson.parser.deserializer.ASMDeserializerFactory|http://code.alibabatech.com/svn/fastjson/trunk/fastjson/src/main/java/com/alibaba/fastjson/parser/deserializer/ASMDeserializerFactory.java],是使用asm針對每種類型的VO動態創建一個類實現的。

Java代碼

// 用於快速匹配的每個字段的前綴
char[] size_ = "\\"size\\":".toCharArray();
char[] uri_ = "\\"uri\\":".toCharArray();
char[] titile_ = "\\"title\\":".toCharArray();
char[] width_ = "\\"width\\":".toCharArray();
char[] height_ = "\\"height\\":".toCharArray();
// 保存parse開始時的lexer狀態信息
int mark = lexer.getBufferPosition();
char mark_ch = lexer.getCurrent();
int mark_token = lexer.token();
int height = lexer.scanFieldInt(height_);
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) {
// 退出快速模式, 進入常規模式
lexer.reset(mark, mark_ch, mark_token);
return (T) super.deserialze(parser, clazz);
}
Size size = Size.valueOf(value);
// ... ...
// batch set
Image image = new Image();
image.setSize(size);
image.setUri(uri);
image.setTitle(title);
image.setWidth(width);
image.setHeight(height);
return (T) image;

為什麼Fastjson能夠做到這麼快

3、使用asm避免反射

deserialize的時候,會使用asm來構造對象,並且做batch set,也就是說合並連續調用多個setter方法,而不是分散調用,這個能夠提升性能。

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。

Java代碼

//com.alibaba.fastjson.JSON源碼
public static final T parseObject(byte[] input, int off, int len, CharsetDecoder charsetDecoder, Type clazz,
Feature... features) {
charsetDecoder.reset();
int scaleLength = (int) (len * (double) charsetDecoder.maxCharsPerByte());
char[] chars = ThreadLocalCache.getChars(scaleLength); // 使用ThreadLocalCache,避免頻繁分配內存
ByteBuffer byteBuf = ByteBuffer.wrap(input, off, len);
CharBuffer charByte = CharBuffer.wrap(chars);
IOUtils.decode(charsetDecoder, byteBuf, charByte);
int position = charByte.position();
return (T) parseObject(chars, position, clazz, features);
}

6、symbolTable算法。

我們看xml或者javac的parser實現,經常會看到有一個這樣的東西symbol table,它就是把一些經常使用的關鍵字緩存起來,在遍歷char[]的時候,同時把hash計算好,通過這個hash值在hashtable中來獲取緩存好的symbol,避免創建新的字符串對象。這種優化在fastjson裡面用在key的讀取,以及enum value的讀取。這是也是parse性能優化的關鍵算法之一。

以下代碼用於讀取類型為enum的value。

Java代碼

int hash = 0; 
for (;;) {
ch = buf[index++];
if (ch == '\\"') {
bp = index;

this.ch = ch = buf[bp];
strVal = symbolTable.addSymbol(buf, start, index - start - 1, hash); // 通過symbolTable來獲得緩存好的symbol,包括fieldName、enumValue
break;
}
hash = 31 * hash + ch; // 在token scan的過程中計算好hash
// ... ...
}

----------------------------

最後,我自己是一名從事了多年開發的JAVA老程序員,今年年初我花了一個月整理了一份最適合2019年學習的java學習乾貨,可以送給每一位喜歡java的小夥伴,想要獲取的可以關注我的頭條號並在後臺私信我:java,即可免費獲取。


為什麼Fastjson能夠做到這麼快


分享到:


相關文章: