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) {
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实现。
(三) - 针对对象实现解析
JSON Token解析
这个章节主要讨论关于对象字段相关词法解析的api。
JSONLexerBase成员函数
这里讲解主要挑选具有代表性的api进行讲解,同时对于极其相似的api不冗余分析,可以参考代码阅读。
Int类型字段解析
当反序列化java对象遇到整型int.class字段会调用该方法解析:
public int scanInt(char expectNext) {
matchStat = UNKNOWN;
int offset = 0;
char chLocal = charAt(bp + (offset++));
/** 取整数第一个字符判断是否是引号 */
final boolean quote = chLocal == '"';
if (quote) {
/** 如果是双引号,取第一个数字字符 */
chLocal = charAt(bp + (offset++));
}
final boolean negative = chLocal == '-';
if (negative) {
/** 如果是负数,继续取下一个字符 */
chLocal = charAt(bp + (offset++));
}
int value;
/** 是数字类型 */
if (chLocal >= '0' && chLocal <= '9') {
value = chLocal - '0';
for (;;) {
/** 循环将字符转换成数字 */
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
value = value * 10 + (chLocal - '0');
} else if (chLocal == '.') {
matchStat = NOT_MATCH;
return 0;
} else {
break;
}
}
if (value < 0) {
matchStat = NOT_MATCH;
return 0;
}
} else if (chLocal == 'n' && charAt(bp + offset) == 'u' && charAt(bp + offset + 1) == 'l' && charAt(bp + offset + 2) == 'l') {
/** 匹配到null */
matchStat = VALUE_NULL;
value = 0;
offset += 3;
/** 读取null后面的一个字符 */
chLocal = charAt(bp + offset++);
if (quote && chLocal == '"') {
chLocal = charAt(bp + offset++);
}
for (;;) {
/** 如果读取null后面有逗号,认为结束 */
if (chLocal == ',') {
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.COMMA;
return value;
} else if (chLocal == ']') {
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.RBRACKET;
return value;
/** 忽略空白字符 */
} else if (isWhitespace(chLocal)) {
chLocal = charAt(bp + offset++);
continue;
}
break;
}
matchStat = NOT_MATCH;
return 0;
} else {
matchStat = NOT_MATCH;
return 0;
}
for (;;) {
/** 根据期望字符用于结束匹配 */
if (chLocal == expectNext) {
bp += offset;
this.ch = this.charAt(bp);
matchStat = VALUE;
token = JSONToken.COMMA;
return negative ? -value : value;
} else {
/** 忽略空白字符 */
if (isWhitespace(chLocal)) {
chLocal = charAt(bp + (offset++));
continue;
}
matchStat = NOT_MATCH;
return negative ? -value : value;
}
}
}
com.alibaba.fastjson.parser.JSONLexerBase#scanInt(char)方法考虑了数字加引号的情况,当遇到下列情况认为匹配失败:
1 扫描遇到的数字遇到标点符号
2 扫描的数字范围溢出
3 扫描到的非数字并且不是null
4 忽略空白字符的情况下,读取数字后结束符和期望expectNext不一致
fastjson 还提供第二种接口,根据token识别数字:
public final Number integerValue() throws NumberFormatException {
long result = 0;
boolean negative = false;
if (np == -1) {
np = 0;
}
/** np是token开始索引, sp是buffer索引,也代表buffer字符个数 */
int i = np, max = np + sp;
long limit;
long multmin;
int digit;
char type = ' ';
/** 探测数字类型最后一位是否带类型 */
switch (charAt(max - 1)) {
case 'L':
max--;
type = 'L';
break;
case 'S':
max--;
type = 'S';
break;
case 'B':
max--;
type = 'B';
break;
default:
break;
}
/** 探测数字首字符是否是符号 */
if (charAt(np) == '-') {
negative = true;
limit = Long.MIN_VALUE;
i++;
} else {
limit = -Long.MAX_VALUE;
}
multmin = MULTMIN_RADIX_TEN;
if (i < max) {
/** 数字第一个字母转换成数字 */
digit = charAt(i++) - '0';
result = -digit;
}
/** 快速处理高精度整数,因为整数最大是10^9次方 */
while (i < max) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = charAt(i++) - '0';
/** multmin 大概10^17 */
if (result < multmin) {
/** numberString获取到的不包含数字后缀类型,但是包括负数符号(如果有) */
return new BigInteger(numberString());
}
result *= 10;
if (result < limit + digit) {
return new BigInteger(numberString());
}
result -= digit;
}
if (negative) {
/** 处理完数字 i 是指向数字最后一个字符的下一个字符,
* 这里判断 i > np + 1 , 代表在 有效数字字符范围
*/
if (i > np + 1) {
/** 这里根据类型具体后缀类型做一次转换 */
if (result >= Integer.MIN_VALUE && type != 'L') {
if (type == 'S') {
return (short) result;
}
if (type == 'B') {
return (byte) result;
}
return (int) result;
}
return result;
} else { /* Only got "-" */
throw new NumberFormatException(numberString());
}
} else {
/** 这里是整数, 因为前面处理成负数,取反就可以了 */
result = -result;
/** 这里根据类型具体后缀类型做一次转换 */
if (result <= Integer.MAX_VALUE && type != 'L') {
if (type == 'S') {
return (short) result;
}
if (type == 'B') {
return (byte) result;
}
return (int) result;
}
return result;
}
}
fastjson
还提供第三种接口,这个接口严格根据字段名进行匹配json
字符串,字段名会自动加上双引号和冒号,格式"key":
:
public int scanFieldInt(char[] fieldName) {
matchStat = UNKNOWN;
/** 属性不匹配,忽略 */
if (!charArrayCompare(fieldName)) {
matchStat = NOT_MATCH_NAME;
return 0;
}
int offset = fieldName.length;
char chLocal = charAt(bp + (offset++));
final boolean negative = chLocal == '-';
if (negative) {
/** 如果是负数,读取第一个数字字符 */
chLocal = charAt(bp + (offset++));
}
int value;
if (chLocal >= '0' && chLocal <= '9') {
/** 转换成数字 */
value = chLocal - '0';
for (;;) {
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
value = value * 10 + (chLocal - '0');
} else if (chLocal == '.') {
/** 数字后面有点,不符合整数,标记不匹配 */
matchStat = NOT_MATCH;
return 0;
} else {
break;
}
}
/** value < 0 代表整数值溢出了,
* 11 + 3 代表了最小负数加了引号(占用2), 剩余
* 占用1 是因为读完最后一位数字,offset++ 递增了1
*/
if (value < 0
|| offset > 11 + 3 + fieldName.length) {
if (value != Integer.MIN_VALUE
|| offset != 17
|| !negative) {
matchStat = NOT_MATCH;
return 0;
}
}
} else {
/** 非数字代表不匹配 */
matchStat = NOT_MATCH;
return 0;
}
/** 如果遇到逗号,认为结束 */
if (chLocal == ',') {
bp += offset;
this.ch = this.charAt(bp);
matchStat = VALUE;
token = JSONToken.COMMA;
return negative ? -value : value;
}
if (chLocal == '}') {
chLocal = charAt(bp + (offset++));
if (chLocal == ',') {
token = JSONToken.COMMA;
bp += offset;
this.ch = this.charAt(bp);
} else if (chLocal == ']') {
token = JSONToken.RBRACKET;
bp += offset;
this.ch = this.charAt(bp);
} else if (chLocal == '}') {
token = JSONToken.RBRACE;
bp += offset;
this.ch = this.charAt(bp);
} else if (chLocal == EOI) {
token = JSONToken.EOF;
bp += (offset - 1);
ch = EOI;
} else {
matchStat = NOT_MATCH;
return 0;
}
matchStat = END;
} else {
matchStat = NOT_MATCH;
return 0;
}
return negative ? -value : value;
}
Long类型字段解析
Long
字段解析和Int
一样提供3中接口,先看第一种基于字段类型解析:
public long scanLong(char expectNextChar) {
matchStat = UNKNOWN;
int offset = 0;
char chLocal = charAt(bp + (offset++));
final boolean quote = chLocal == '"';
if (quote) {
/** 有引号,继续读下一个字符 */
chLocal = charAt(bp + (offset++));
}
final boolean negative = chLocal == '-';
if (negative) {
/** 有符号,标识是负数 */
chLocal = charAt(bp + (offset++));
}
long value;
/** 循环将字符转换成数字 */
if (chLocal >= '0' && chLocal <= '9') {
value = chLocal - '0';
for (;;) {
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
value = value * 10 + (chLocal - '0');
} else if (chLocal == '.') {
matchStat = NOT_MATCH;
return 0;
} else {
break;
}
}
/** 如果偏移量超过最大long的21位,是无效数字 */
boolean valid = value >= 0 || (value == -9223372036854775808L && negative);
if (!valid) {
String val = subString(bp, offset - 1);
throw new NumberFormatException(val);
}
} else if (chLocal == 'n' && charAt(bp + offset) == 'u' && charAt(bp + offset + 1) == 'l' && charAt(bp + offset + 2) == 'l') {
matchStat = VALUE_NULL;
value = 0;
offset += 3;
chLocal = charAt(bp + offset++);
if (quote && chLocal == '"') {
chLocal = charAt(bp + offset++);
}
for (;;) {
if (chLocal == ',') {
/** 如果是null, 紧跟着逗号,认为结束匹配 */
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.COMMA;
return value;
} else if (chLocal == ']') {
/** 如果是null, 紧跟着逗号], 认为结束匹配 */
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.RBRACKET;
return value;
} else if (isWhitespace(chLocal)) {
chLocal = charAt(bp + offset++);
continue;
}
break;
}
matchStat = NOT_MATCH;
return 0;
} else {
matchStat = NOT_MATCH;
return 0;
}
if (quote) {
if (chLocal != '"') {
matchStat = NOT_MATCH;
return 0;
} else {
chLocal = charAt(bp + (offset++));
}
}
/**
* 忽略和Int一致的根据期望字符判断逻辑
*/
}
因为和Int
比较相似,这里提供第三个基于字段名字匹配实现:
public long scanFieldLong(char[] fieldName) {
matchStat = UNKNOWN;
/**
* 从当前json串bp位置开始逐字符比较字段 是否匹配
*
* fieldName 格式是 "name":
* @see FieldInfo#genFieldNameChars()
*/
if (!charArrayCompare(fieldName)) {
matchStat = NOT_MATCH_NAME;
return 0;
}
int offset = fieldName.length;
char chLocal = charAt(bp + (offset++));
boolean negative = false;
if (chLocal == '-') {
/** 有符号,标识是负数 */
chLocal = charAt(bp + (offset++));
negative = true;
}
long value;
if (chLocal >= '0' && chLocal <= '9') {
value = chLocal - '0';
for (;;) {
/** 循环将字符转换成数字 */
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
value = value * 10 + (chLocal - '0');
/** 如果数字带标点符号,认为不是合法整数,匹配失败 */
} else if (chLocal == '.') {
matchStat = NOT_MATCH;
return 0;
} else {
break;
}
}
/** 如果偏移量超过最大long的21位,是无效数字 */
boolean valid = offset - fieldName.length < 21
&& (value >= 0 || (value == -9223372036854775808L && negative));
if (!valid) {
matchStat = NOT_MATCH;
return 0;
}
} else {
matchStat = NOT_MATCH;
return 0;
}
if (chLocal == ',') {
/** 如果数字后面跟着逗号,结束 并预读下一个字符 */
bp += offset;
this.ch = this.charAt(bp);
matchStat = VALUE;
token = JSONToken.COMMA;
return negative ? -value : value;
}
/**
* 忽略和Int一致的判断数字后续的token逻辑
*/
return negative ? -value : value;
}
Float类型字段解析
跟Int
一致的接口,现提供第二种获取float
实现:
public float floatValue() {
/** numberString获取到的不包含数字后缀类型,但是包括负数符号(如果有) */
String strVal = numberString();
float floatValue = Float.parseFloat(strVal);
/** 如果是0或者正无穷大,首字母是0-9 代表溢出 */
if (floatValue == 0 || floatValue == Float.POSITIVE_INFINITY) {
char c0 = strVal.charAt(0);
if (c0 > '0' && c0 <= '9') {
throw new JSONException("float overflow : " + strVal);
}
}
return floatValue;
}
提供根据属性字段名字匹配的源码实现:
public final float scanFieldFloat(char[] fieldName) {
matchStat = UNKNOWN;
if (!charArrayCompare(fieldName)) {
matchStat = NOT_MATCH_NAME;
return 0;
}
int offset = fieldName.length;
char chLocal = charAt(bp + (offset++));
final boolean quote = chLocal == '"';
if (quote) {
chLocal = charAt(bp + (offset++));
}
boolean negative = chLocal == '-';
if (negative) {
chLocal = charAt(bp + (offset++));
}
float value;
if (chLocal >= '0' && chLocal <= '9') {
int intVal = chLocal - '0';
for (;;) {
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
intVal = intVal * 10 + (chLocal - '0');
continue;
} else {
/** 如果遇到非数字字符终止 */
break;
}
}
int power = 1;
boolean small = (chLocal == '.');
if (small) {
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
/** 将小数点后面数字转换成int类型数字 */
intVal = intVal * 10 + (chLocal - '0');
power = 10;
for (;;) {
chLocal = charAt(bp + (offset++));
if (chLocal >= '0' && chLocal <= '9') {
/** 依次读取数字并转化int,记录小数点的数量级 */
intVal = intVal * 10 + (chLocal - '0');
power *= 10;
continue;
} else {
break;
}
}
} else {
matchStat = NOT_MATCH;
return 0;
}
}
boolean exp = chLocal == 'e' || chLocal == 'E';
if (exp) {
/** 处理科学计数法 */
chLocal = charAt(bp + (offset++));
if (chLocal == '+' || chLocal == '-') {
chLocal = charAt(bp + (offset++));
}
for (;;) {
if (chLocal >= '0' && chLocal <= '9') {
chLocal = charAt(bp + (offset++));
} else {
break;
}
}
}
int start, count;
if (quote) {
if (chLocal != '"') {
matchStat = NOT_MATCH;
return 0;
} else {
/** 遇到浮点数最后一个引号,预读下一个 */
chLocal = charAt(bp + (offset++));
}
/**
* ----------------------------------------------------------------------------------------
* | { | " | k | e | y | " | : | " | 7 | 0 | 0 | 8 | . | 5 | 5 | 5 | 5 | " | }
* ----------------------------------------------------------------------------------------
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18
* ----------------------------------------------------------------------------------------
* | | bp | | | | | | |start| | | | | | | | | | offset
* ----------------------------------------------------------------------------------------
* fieldName = "key":
* fieldName.length == 6, bp == 0, offset == 17
* start代表指向浮点第一个数字或者-号,
* @see com.alibaba.json.bvt.parser.deser.BooleanFieldDeserializerTest#test_2()
*/
start = bp + fieldName.length + 1;
count = bp + offset - start - 2;
} else {
start = bp + fieldName.length;
count = bp + offset - start - 1;
}
if (!exp && count < 20) {
value = ((float) intVal) / power;
if (negative) {
value = -value;
}
} else {
String text = this.subString(start, count);
value = Float.parseFloat(text);
}
} else if (chLocal == 'n' && charAt(bp + offset) == 'u' && charAt(bp + offset + 1) == 'l' && charAt(bp + offset + 2) == 'l') {
matchStat = VALUE_NULL;
value = 0;
offset += 3;
chLocal = charAt(bp + offset++);
if (quote && chLocal == '"') {
chLocal = charAt(bp + offset++);
}
for (;;) {
if (chLocal == ',') {
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.COMMA;
return value;
} else if (chLocal == '}') {
bp += offset;
this.ch = charAt(bp);
matchStat = VALUE_NULL;
token = JSONToken.RBRACE;
return value;
} else if (isWhitespace(chLocal)) {
chLocal = charAt(bp + offset++);
continue;
}
break;
}
matchStat = NOT_MATCH;
return 0;
} else {
matchStat = NOT_MATCH;
return 0;
}
if (chLocal == ',') {
bp += offset;
this.ch = this.charAt(bp);
matchStat = VALUE;
token = JSONToken.COMMA;
return value;
}
/**
* 省略读取数字后,剩余token匹配逻辑
*/
return value;
}
String类型字段解析
public String scanString(char expectNextChar) {
matchStat = UNKNOWN;
int offset = 0;
char chLocal = charAt(bp + (offset++));
/** 兼容处理null字符串 */
if (chLocal == 'n') {
if (charAt(bp + offset) == 'u' && charAt(bp + offset + 1) == 'l' && charAt(bp + offset + 2) == 'l') {
offset += 3;
chLocal = charAt(bp + (offset++));
} else {
matchStat = NOT_MATCH;
return null;
}
if (chLocal == expectNextChar) {
bp += offset;
this.ch = this.charAt(bp);
matchStat = VALUE;
return null;
} else {
matchStat = NOT_MATCH;
return null;
}
}
final String strVal;
for (;;) {
if (chLocal == '"') {
int startIndex = bp + offset;
int endIndex = indexOf('"', startIndex);
if (endIndex == -1) {
throw new JSONException("unclosed str");
}
String stringVal = subString(bp + offset, endIndex - startIndex);
/**
* 处理逻辑请参考详细注释:
* @see ##scanFieldString(char[])
*/
if (stringVal.indexOf('\\') != -1) {
for (; ; ) {
int slashCount = 0;
for (int i = endIndex - 1; i >= 0; --i) {
if (charAt(i) == '\\') {
slashCount++;
} else {
break;
}
}
if (slashCount % 2 == 0) {
break;
}
endIndex = indexOf('"', endIndex + 1);
}
int chars_len = endIndex - startIndex;
char[] chars = sub_chars(bp + 1, chars_len);
stringVal = readString(chars, chars_len);
}
offset += (endIndex - startIndex + 1);
chLocal = charAt(bp + (offset++));
strVal = stringVal;
break;
} else if (isWhitespace(chLocal)) {
chLocal = charAt(bp + (offset++));
continue;
} else {
matchStat = NOT_MATCH;
return stringDefaultValue();
}
}
for (;;) {
/** 如果遇到和期望字符认为结束符 */
if (chLocal == expectNextChar) {
bp += offset;
/** 预读下一个字符 */
this.ch = charAt(bp);
matchStat = VALUE;
return strVal;
} else if (isWhitespace(chLocal)) {
chLocal = charAt(bp + (offset++));
continue;
} else {
matchStat = NOT_MATCH;
return strVal;
}
}
}
目前已经分析足够多的此法分析代码,可以先自己分析或者参考下方更详细scanFieldString实现。
public abstract String stringVal();
这里提供的stringVal()需要由子类实现,原因:
1 在android6.0和jdk6版本 获取子字符串会共享外层String的char[] 会导致String占用内存无法释放(特别是打文本字符串)。
public String scanFieldString(char[] fieldName) {
matchStat = UNKNOWN;
/**
* 从当前json串bp位置开始逐字符比较字段 是否匹配
*
* fieldName 格式是 "name":
* @see FieldInfo#genFieldNameChars()
*/
if (!charArrayCompare(fieldName)) {
matchStat = NOT_MATCH_NAME;
return stringDefaultValue();
}
// int index = bp + fieldName.length;
int offset = fieldName.length;
/** 读取字段下一个字符 */
char chLocal = charAt(bp + (offset++));
/** json 值类型字符串一定",否则不符合规范 */
if (chLocal != '"') {
matchStat = NOT_MATCH;
return stringDefaultValue();
}
final String strVal;
{
/** startIndex指向双引号下一个字符,
* eg : "name":"string", startIndex指向s
*/
int startIndex = bp + fieldName.length + 1;
int endIndex = indexOf('"', startIndex);
if (endIndex == -1) {
throw new JSONException("unclosed str");
}
int startIndex2 = bp + fieldName.length + 1; // must re compute
String stringVal = subString(startIndex2, endIndex - startIndex2);
/** 包含特殊转译字符 */
if (stringVal.indexOf('\\') != -1) {
/**
* 处理场景 "value\\\"" json串值
*/
for (;;) {
int slashCount = 0;
for (int i = endIndex - 1; i >= 0; --i) {
if (charAt(i) == '\\') {
slashCount++;
} else {
break;
}
}
if (slashCount % 2 == 0) {
break;
}
/** 如果遇到奇数转译字符,遇到"不认为值结束,找下一个"才认为结束 */
endIndex = indexOf('"', endIndex + 1);
}
/**
* ---------------------------------------------------------------------------------
* | " | k | e | y | " | : | " | v | a | l | u | e | \ | \ | \ | " | " |
* ---------------------------------------------------------------------------------
* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
* ---------------------------------------------------------------------------------
* | bp | | | | | | | | | | | | | | | | endIndex |
* ---------------------------------------------------------------------------------
* fieldName = "key":
* fieldName.length == 6, bp == 0, endIndex == 16
* chars_len = 16 - (0 + 6 + 1) = 9, == value\\\"
*/
int chars_len = endIndex - (bp + fieldName.length + 1);
char[] chars = sub_chars( bp + fieldName.length + 1, chars_len);
stringVal = readString(chars, chars_len);
}
/** 偏移到json串字段值" 下一个字符 */
offset += (endIndex - (bp + fieldName.length + 1) + 1);
chLocal = charAt(bp + (offset++));
strVal = stringVal;
}
if (chLocal == ',') {
bp += offset;
/** 读取下一个字符 */
this.ch = this.charAt(bp);
matchStat = VALUE;
return strVal;
}
if (chLocal == '}') {
chLocal = charAt(bp + (offset++));
/** 如果字段值紧跟, 标记下次token为逗号 */
if (chLocal == ',') {
token = JSONToken.COMMA;
bp += offset;
this.ch = this.charAt(bp);
/** 如果字段值紧跟] 标记下次token为右中括号 */
} else if (chLocal == ']') {
token = JSONToken.RBRACKET;
bp += offset;
this.ch = this.charAt(bp);
/** 如果字段值紧跟} 标记下次token为右花括号 */
} else if (chLocal == '}') {
token = JSONToken.RBRACE;
bp += offset;
this.ch = this.charAt(bp);
/** 特殊标记结束 */
} else if (chLocal == EOI) {
token = JSONToken.EOF;
bp += (offset - 1);
ch = EOI;
} else {
matchStat = NOT_MATCH;
return stringDefaultValue();
}
matchStat = END;
} else {
matchStat = NOT_MATCH;
return stringDefaultValue();
}
return strVal;
}
目前分析的代码其实包括大部分实现了,这里没有给出Decimal和Double的实现,它们实现是类似的并且相对简单,主要是提取字符串直接用对应类的构造函数生成对象而已,如果想详细了解可以参考代码中已经添加的详尽注释。
终于要结束词法分析相关api接口的分析了,这个是词法分析非常重要的基础实现,有继承这个类的两种实现com.alibaba.fastjson.parser.JSONScanner和com.alibaba.fastjson.parser.JSONReaderScanner, 这两个类继承主要增加一个优化的措施,后面讲解反序列化实现的时候会对相关重写的方法进行补充。