fastjson深度原始碼解析- 詞法和語法解析(二)
阿新 • • 發佈:2019-01-27
JSON Token解析
JSONLexerBase
定義並實現了json
串實現解析機制的基礎,在理解後面反序列化之前,我們先來看看並理解重要的屬性:
/** 當前token含義 */
protected int token;
/** 記錄當前掃描字元位置 */
protected int pos;
protected int features;
/** 當前有效字元 */
protected char ch;
/** 流(或者json字串)中當前的位置,每次讀取字元會遞增 */
protected int bp;
protected int eofPos;
/** 字元緩衝區 */
protected char[] sbuf;
/** 字元緩衝區的索引,指向下一個可寫
* 字元的位置,也代表字元緩衝區字元數量
*/
protected int sp;
/**
* number start position
* 可以理解為 找到token時 token的首字元位置
* 和bp不一樣,這個不會遞增,會在開始token前記錄一次
*/
protected int np;
JSONLexerBase成員函式
在開始分析詞法分析實現過程中,我發現中解析存在大量重複程式碼實現或極其類似實現,重複程式碼主要解決類似c++內聯呼叫,極其相似程式碼實現我會挑選有代表性的來說明(一般實現較為複雜),沒有說明的成員函式可以參考程式碼註釋。
推斷token型別
fastjson
token型別推斷當前json
字串是那種型別的token, 比如是字串、花括號和逗號等等。
public final void nextToken() {
/** 將字元buffer pos設定為初始0 */
sp = 0;
for (;;) {
/** pos記錄為流的當前位置 */
pos = bp;
if (ch == '/') {
/** 如果是註釋// 或者 \/* *\/ 註釋,跳過註釋 */
skipComment();
continue;
}
if (ch == '"') {
/** 讀取引號內的字串 */
scanString();
return;
}
if (ch == ',') {
/** 跳過當前,讀取下一個字元 */
next();
token = COMMA;
return;
}
if (ch >= '0' && ch <= '9') {
/** 讀取整數 */
scanNumber();
return;
}
if (ch == '-') {
/** 讀取負數 */
scanNumber();
return;
}
switch (ch) {
/** 讀取單引號後面的字串,和scanString邏輯一致 */
case '\'':
if (!isEnabled(Feature.AllowSingleQuotes)) {
throw new JSONException("Feature.AllowSingleQuotes is false");
}
scanStringSingleQuote();
return;
case ' ':
case '\t':
case '\b':
case '\f':
case '\n':
case '\r':
next();
break;
case 't': // true
/** 讀取字元true */
scanTrue();
return;
case 'f': // false
/** 讀取字元false */
scanFalse();
return;
case 'n': // new,null
/** 讀取為new或者null的token */
scanNullOrNew();
return;
case 'T':
case 'N': // NULL
case 'S':
case 'u': // undefined
/** 讀取識別符號,已經自動預讀了下一個字元 */
scanIdent();
return;
case '(':
/** 讀取下一個字元 */
next();
token = LPAREN;
return;
case ')':
next();
token = RPAREN;
return;
case '[':
next();
token = LBRACKET;
return;
case ']':
next();
token = RBRACKET;
return;
case '{':
next();
token = LBRACE;
return;
case '}':
next();
token = RBRACE;
return;
case ':':
next();
token = COLON;
return;
case ';':
next();
token = SEMI;
return;
case '.':
next();
token = DOT;
return;
case '+':
next();
scanNumber();
return;
case 'x':
scanHex();
return;
default:
if (isEOF()) { // JLS
if (token == EOF) {
throw new JSONException("EOF error");
}
token = EOF;
pos = bp = eofPos;
} else {
/** 忽略控制字元或者刪除字元 */
if (ch <= 31 || ch == 127) {
next();
break;
}
lexError("illegal.char", String.valueOf((int) ch));
next();
}
return;
}
}
}
跳過註釋
protected void skipComment() {
/** 讀下一個字元 */
next();
/** 連續遇到左反斜槓/ */
if (ch == '/') {
for (;;) {
/** 讀下一個字元 */
next();
if (ch == '\n') {
/** 如果遇到換行符,繼續讀取下一個字元並返回 */
next();
return;
/** 如果已經遇到流結束,返回 */
} else if (ch == EOI) {
return;
}
}
/** 遇到`/*` 註釋的格式 */
} else if (ch == '*') {
/** 讀下一個字元 */
next();
for (; ch != EOI;) {
if (ch == '*') {
/** 如果遇到*,繼續嘗試讀取下一個字元,看看是否是/字元 */
next();
if (ch == '/') {
/** 如果確實是/字元,提前預讀下一個有效字元後終止 */
next();
return;
} else {
/** 遇到非/ 繼續跳過度下一個字元 */
continue;
}
}
/** 如果沒有遇到`*\` 註釋格式, 繼續讀下一個字元 */
next();
}
} else {
/** 不符合// 或者 \/* *\/ 註釋格式 */
throw new JSONException("invalid comment");
}
}
解析註釋主要分為2中,支援//
或者 /* */
註釋格式。
掃描字串
當解析json
字串是"
時,會呼叫掃描字串方法。
public final void scanString() {
/** 記錄當前流中token的開始位置, np指向引號的索引 */
np = bp;
hasSpecial = false;
char ch;
for (;;) {
/** 讀取當前字串的字元 */
ch = next();
/** 如果遇到字串結束符", 則結束 */
if (ch == '\"') {
break;
}
if (ch == EOI) {
/** 如果遇到了結束符EOI,但是沒有遇到流的結尾,新增EOI結束符 */
if (!isEOF()) {
putChar((char) EOI);
continue;
}
throw new JSONException("unclosed string : " + ch);
}
/** 處理轉譯字元邏輯 */
if (ch == '\\') {
if (!hasSpecial) {
/** 第一次遇到\認為是特殊符號 */
hasSpecial = true;
/** 如果buffer空間不夠,執行2倍擴容 */
if (sp >= sbuf.length) {
int newCapcity = sbuf.length * 2;
if (sp > newCapcity) {
newCapcity = sp;
}
char[] newsbuf = new char[newCapcity];
System.arraycopy(sbuf, 0, newsbuf, 0, sbuf.length);
sbuf = newsbuf;
}
/** 複製有效字串到buffer中,不包括引號 */
copyTo(np + 1, sp, sbuf);
// text.getChars(np + 1, np + 1 + sp, sbuf, 0);
// System.arraycopy(buf, np + 1, sbuf, 0, sp);
}
/** 讀取轉譯字元\下一個字元 */
ch = next();
/** 轉換ascii字元,請參考:https://baike.baidu.com/item/ASCII/309296?fr=aladdin */
switch (ch) {
case '0':
/** 空字元 */
putChar('\0');
break;
case '1':
/** 標題開始 */
putChar('\1');
break;
case '2':
/** 正文開始 */
putChar('\2');
break;
case '3':
/** 正文結束 */
putChar('\3');
break;
case '4':
/** 傳輸結束 */
putChar('\4');
break;
case '5':
/** 請求 */
putChar('\5');
break;
case '6':
/** 收到通知 */
putChar('\6');
break;
case '7':
/** 響鈴 */
putChar('\7');
break;
case 'b': // 8
/** 退格 */
putChar('\b');
break;
case 't': // 9
/** 水平製表符 */
putChar('\t');
break;
case 'n': // 10
/** 換行鍵 */
putChar('\n');
break;
case 'v': // 11
/** 垂直製表符 */
putChar('\u000B');
break;
case 'f': // 12
/** 換頁鍵 */
case 'F':
/** 換頁鍵 */
putChar('\f');
break;
case 'r': // 13
/** 回車鍵 */
putChar('\r');
break;
case '"': // 34
/** 雙引號 */
putChar('"');
break;
case '\'': // 39
/** 閉單引號 */
putChar('\'');
break;
case '/': // 47
/** 斜槓 */
putChar('/');
break;
case '\\': // 92
/** 反斜槓 */
putChar('\\');
break;
case 'x':
/** 小寫字母x, 標識一個字元 */
char x1 = ch = next();
char x2 = ch = next();
/** x1 左移4位 + x2 */
int x_val = digits[x1] * 16 + digits[x2];
char x_char = (char) x_val;
putChar(x_char);
break;
case 'u':
/** 小寫字母u, 標識一個字元 */
char u1 = ch = next();
char u2 = ch = next();
char u3 = ch = next();
char u4 = ch = next();
int val = Integer.parseInt(new String(new char[] { u1, u2, u3, u4 }), 16);
putChar((char) val);
break;
default:
this.ch = ch;
throw new JSONException("unclosed string : " + ch);
}
continue;
}
/** 沒有轉譯字元,遞增buffer字元位置 */
if (!hasSpecial) {
sp++;
continue;
}
/** 繼續讀取轉譯字元後面的字元 */
if (sp == sbuf.length) {
putChar(ch);
} else {
sbuf[sp++] = ch;
}
}
token = JSONToken.LITERAL_STRING;
/** 自動預讀下一個字元 */
this.ch = next();
}
解析到字串的時候會寫入buffer。
掃描數字型別
public final void scanNumber() {
/** 記錄當前流中token的開始位置, np指向數字字元索引 */
np = bp;
/** 相容處理負數 */
if (ch == '-') {
sp++;
next();
}
for (;;) {
if (ch >= '0' && ch <= '9') {
/** 如果是數字字元,遞增索引位置 */
sp++;
} else {
break;
}
next();
}
boolean isDouble = false;
/** 如果遇到小數點字元 */
if (ch == '.') {
sp++;
/** 繼續讀小數點後面字元 */
next();
isDouble = true;
for (;;) {
if (ch >= '0' && ch <= '9') {
sp++;
} else {
break;
}
next();
}
}
/** 繼續讀取數字後面的型別 */
if (ch == 'L') {
sp++;
next();
} else if (ch == 'S') {
sp++;
next();
} else if (ch == 'B') {
sp++;
next();
} else if (ch == 'F') {
sp++;
next();
isDouble = true;
} else if (ch == 'D') {
sp++;
next();
isDouble = true;
} else if (ch == 'e' || ch == 'E') {
/** 掃描科學計數法 */
sp++;
next();
if (ch == '+' || ch == '-') {
sp++;
next();
}
for (;;) {
if (ch >= '0' && ch <= '9') {
sp++;
} else {
break;
}
next();
}
if (ch == 'D' || ch == 'F') {
sp++;
next();
}
isDouble = true;
}
if (isDouble) {
token = JSONToken.LITERAL_FLOAT;
} else {
token = JSONToken.LITERAL_INT;
}
}
掃描Boolean
public final void scanTrue() {
if (ch != 't') {
throw new JSONException("error parse true");
}
next();
if (ch != 'r') {
throw new JSONException("error parse true");
}
next();
if (ch != 'u') {
throw new JSONException("error parse true");
}
next();
if (ch != 'e') {
throw new JSONException("error parse true");
}
next();
if (ch == ' ' || ch == ',' || ch == '}' || ch == ']' || ch == '\n' || ch == '\r' || ch == '\t' || ch == EOI
|| ch == '\f' || ch == '\b' || ch == ':' || ch == '/') {
/** 相容性防禦,標記是true的token */
token = JSONToken.TRUE;
} else {
throw new JSONException("scan true error");
}
}
掃描識別符號
public final void scanIdent() {
/** 記錄當前流中token的開始位置, np指向當前token前一個字元 */
np = bp - 1;
hasSpecial = false;
for (;;) {
sp++;
next();
/** 如果是字母或數字,繼續讀取 */
if (Character.isLetterOrDigit(ch)) {
continue;
}
/** 獲取字串值 */
String ident = stringVal();
if ("null".equalsIgnoreCase(ident)) {
token = JSONToken.NULL;
} else if ("new".equals(ident)) {
token = JSONToken.NEW;
} else if ("true".equals(ident)) {
token = JSONToken.TRUE;
} else if ("false".equals(ident)) {
token = JSONToken.FALSE;
} else if ("undefined".equals(ident)) {
token = JSONToken.UNDEFINED;
} else if ("Set".equals(ident)) {
token = JSONToken.SET;
} else if ("TreeSet".equals(ident)) {
token = JSONToken.TREE_SET;
} else {
token = JSONToken.IDENTIFIER;
}
return;
}
}
掃描十六進位制數
public final void scanHex() {
if (ch != 'x') {
throw new JSONException("illegal state. " + ch);
}
next();
/** 十六進位制x緊跟著單引號 */
/** @see com.alibaba.fastjson.serializer.SerializeWriter#writeHex(byte[]) */
if (ch != '\'') {
throw new JSONException("illegal state. " + ch);
}
np = bp;
/** 這裡一次next, for迴圈也讀一次next, 因為十六進位制被寫成2個位元組的單字元 */
next();
for (int i = 0;;++i) {
char ch = next();
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) {
sp++;
continue;
} else if (ch == '\'') {
sp++;
/** 遇到結束符號,自動預讀下一個字元 */
next();
break;
} else {
throw new JSONException("illegal state. " + ch);
}
}
token = JSONToken.HEX;
}
根據期望字元掃描token
public final void nextToken(int expect) {
/** 將字元buffer pos設定為初始0 */
sp = 0;
for (;;) {
switch (expect) {
case JSONToken.LBRACE:
if (ch == '{') {
token = JSONToken.LBRACE;
next();
return;
}
if (ch == '[') {
token = JSONToken.LBRACKET;
next();
return;
}
break;
case JSONToken.COMMA:
if (ch == ',') {
token = JSONToken.COMMA;
next();
return;
}
if (ch == '}') {
token = JSONToken.RBRACE;
next();
return;
}
if (ch == ']') {
token = JSONToken.RBRACKET;
next();
return;
}
if (ch == EOI) {
token = JSONToken.EOF;
return;
}
break;
case JSONToken.LITERAL_INT:
if (ch >= '0' && ch <= '9') {
pos = bp;
scanNumber();
return;
}
if (ch == '"') {
pos = bp;
scanString();
return;
}
if (ch == '[') {
token = JSONToken.LBRACKET;
next();
return;
}
if (ch == '{') {
token = JSONToken.LBRACE;
next();
return;
}
break;
case JSONToken.LITERAL_STRING:
if (ch == '"') {
pos = bp;
/** 掃描字串, pos指向字串引號索引 */
scanString();
return;
}
if (ch >= '0' && ch <= '9') {
pos = bp;
/** 掃描數字, 前面已經分析過 */
scanNumber();
return;
}
if (ch == '[') {
token = JSONToken.LBRACKET;
next();
return;
}
if (ch == '{') {
token = JSONToken.LBRACE;
next();
return;
}
break;
case JSONToken.LBRACKET:
if (ch == '[') {
token = JSONToken.LBRACKET;
next();
return;
}
if (ch == '{') {
token = JSONToken.LBRACE;
next();
return;
}
break;
case JSONToken.RBRACKET:
if (ch == ']') {
token = JSONToken.RBRACKET;
next();
return;
}
case JSONToken.EOF:
if (ch == EOI) {
token = JSONToken.EOF;
return;
}
break;
case JSONToken.IDENTIFIER:
/** 跳過空白字元,如果是識別符號_、$和字母開頭,否則自動獲取下一個token */
nextIdent();
return;
default:
break;
}
/** 跳過空白字元 */
if (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f' || ch == '\b') {
next();
continue;
}
/** 針對其他token自動讀取下一個, 比如遇到冒號:,自動下一個token */
nextToken();
break;
}
}
這個方法主要是根據期望的字元expect,判定expect對應的token, 接下來主要分析解析物件欄位的相關api實現。