fastjson词法和语法解析
目录
一 token定义解析
词法分析是反序列化的重要基础,在其他框架druid
、parsii
等框架都用到了词法分析的技术,个人认为在讲反序列化之前讲词法分析更重要。
写出优秀框架之前,先理解大量优秀框架的实现对未来自己写框架非常有帮助!!!
好了,废话不多说,来看看fastjson
中定义的token吧。
JSONToken成员
com.alibaba.fastjson.parser.JSONToken
定义了fastjson需要的token标识符:
/** 1 关联到 error */
public final static int ERROR = 1;
/** 2 关联到 int */
public final static int LITERAL_INT = 2;
/** 3 关联到 float */
public final static int LITERAL_FLOAT = 3;
/** 4 关联到 string */
public final static int LITERAL_STRING = 4;
/** 5 关联到 iso8601 */
public final static int LITERAL_ISO8601_DATE = 5;
/** 6 关联到 true */
public final static int TRUE = 6;
/** 7 关联到 false */
public final static int FALSE = 7;
/** 8 关联到 null */
public final static int NULL = 8;
/** 9 关联到 new */
public final static int NEW = 9;
/** 10 关联到 ( */
public final static int LPAREN = 10;
/** 11 关联到 ) */
public final static int RPAREN = 11;
/** 12 关联到 { */
public final static int LBRACE = 12;
/** 13 关联到 } */
public final static int RBRACE = 13;
/** 14 关联到 [ */
public final static int LBRACKET = 14;
/** 15 关联到 ] */
public final static int RBRACKET = 15;
/** 16 关联到 , */
public final static int COMMA = 16;
/** 17 关联到 : */
public final static int COLON = 17;
/** 18 关联到 ident */
public final static int IDENTIFIER = 18;
/** 19 关联到 fieldName */
public final static int FIELD_NAME = 19;
/** 20 关联到 EOF */
public final static int EOF = 20;
/** 21 关联到 Set */
public final static int SET = 21;
/** 22 关联到 TreeSet */
public final static int TREE_SET = 22;
/** 23 关联到 undefined */
public final static int UNDEFINED = 23; // undefined
/** 24 关联到 ; */
public final static int SEMI = 24;
/** 25 关联到 . */
public final static int DOT = 25;
/** 26 关联到 hex */
public final static int HEX = 26;
public static String name(int value) {
switch (value) {
case ERROR:
return "error";
case LITERAL_INT:
return "int";
case LITERAL_FLOAT:
return "float";
case LITERAL_STRING:
return "string";
case LITERAL_ISO8601_DATE:
return "iso8601";
case TRUE:
return "true";
case FALSE:
return "false";
case NULL:
return "null";
case NEW:
return "new";
case LPAREN:
return "(";
case RPAREN:
return ")";
case LBRACE:
return "{";
case RBRACE:
return "}";
case LBRACKET:
return "[";
case RBRACKET:
return "]";
case COMMA:
return ",";
case COLON:
return ":";
case SEMI:
return ";";
case DOT:
return ".";
case IDENTIFIER:
return "ident";
case FIELD_NAME:
return "fieldName";
case EOF:
return "EOF";
case SET:
return "Set";
case TREE_SET:
return "TreeSet";
case UNDEFINED:
return "undefined";
case HEX:
return "hex";
default:
return "Unknown";
}
}
(二) - 基础类型实现解析
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) {
n