1. 程式人生 > >fastjson深度原始碼解析- 詞法和語法解析(二)

fastjson深度原始碼解析- 詞法和語法解析(二)

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實現。