Android FastJson词法分析器(Lexer)源码解析与原理剖析
一、FastJson词法分析器概述
在FastJson解析JSON数据的流程中,词法分析器(Lexer)是最基础且关键的环节。它的核心任务是将输入的JSON字符流拆解为一个个独立的词法单元(Token),为后续的语法分析和语义分析提供基础数据。FastJson的词法分析器基于有限状态自动机(FSA)原理设计,通过高效的字符扫描和状态转移策略,快速识别JSON数据中的字符串、数字、布尔值、对象括号等元素。在Android平台上,这种设计有助于在资源受限的移动设备中实现快速且低开销的JSON解析。
二、词法单元(Token)的定义与分类
1. Token类型的枚举定义
FastJson通过JSONToken
枚举类定义了多种词法单元类型,这些类型涵盖了JSON数据的所有基础元素:
public enum JSONToken {// 特殊符号LITERAL_LEFT_BRACE('{'), // 左大括号,表示JSON对象开始LITERAL_RIGHT_BRACE('}'), // 右大括号,表示JSON对象结束LITERAL_LEFT_BRACKET('['), // 左方括号,表示JSON数组开始LITERAL_RIGHT_BRACKET(']'),// 右方括号,表示JSON数组结束LITERAL_COLON(':'), // 冒号,用于分隔键值对LITERAL_COMMA(','), // 逗号,用于分隔数组元素或对象属性// 数据类型LITERAL_STRING('"'), // 字符串类型LITERAL_NUMBER('0'), // 数字类型LITERAL_TRUE, // 布尔值trueLITERAL_FALSE, // 布尔值falseLITERAL_NULL, // null值// 特殊标记EOF(-1), // 输入流结束标记UNDEFINED; // 未定义的Token类型private final int token;JSONToken(int token) { this.token = token; }JSONToken(char token) { this.token = (int) token; }public int value() { return token; }
}
这些枚举值不仅定义了Token的类型,还通过value
方法关联具体的字符或标识,方便在解析过程中进行快速判断。
2. Token的语义含义
每个Token类型对应JSON数据中的特定语义:
- 对象与数组边界:
LITERAL_LEFT_BRACE
和LITERAL_RIGHT_BRACE
界定JSON对象范围,LITERAL_LEFT_BRACKET
和LITERAL_RIGHT_BRACKET
界定JSON数组范围。 - 数据分隔符:
LITERAL_COLON
用于分隔对象中的键值对,LITERAL_COMMA
用于分隔数组元素或对象中的多个属性。 - 数据值类型:
LITERAL_STRING
表示字符串,LITERAL_NUMBER
表示数值,LITERAL_TRUE
和LITERAL_FALSE
表示布尔值,LITERAL_NULL
表示空值。
三、JSONReader:词法分析器的核心实现类
1. JSONReader的核心成员变量
JSONReader
类是FastJson词法分析的核心载体,其内部维护了多个关键成员变量用于字符流处理:
public class JSONReader {private Reader reader; // 输入字符流(如StringReader)private int pos; // 当前字符在缓冲区中的位置private char[] buffer; // 字符缓冲区private int bufferPos; // 缓冲区中已读取的位置private int bufferSize; // 缓冲区的总大小private int token; // 当前解析出的Token类型private String text; // 存储解析过程中的临时文本// 其他辅助方法和变量...
}
reader
负责读取原始JSON数据,buffer
用于缓存数据以减少IO操作,pos
、bufferPos
和bufferSize
协同管理缓冲区的读取与填充,token
记录当前解析的Token类型。
2. 字符读取与缓冲区管理
JSONReader
通过next
方法读取单个字符,并在缓冲区不足时自动填充:
private int next() {if (bufferPos >= bufferSize) {fillBuffer(); // 缓冲区耗尽时重新填充}return buffer[bufferPos++]; // 返回当前字符并移动读取位置
}private void fillBuffer() {try {int newSize = bufferSize * 2; // 缓冲区扩容为原来的2倍char[] newBuffer = new char[newSize];if (buffer != null) {System.arraycopy(buffer, 0, newBuffer, 0, bufferSize);}buffer = newBuffer;int len = reader.read(buffer, bufferSize, newSize - bufferSize);if (len == -1) {bufferSize = bufferPos;} else {bufferSize += len;}} catch (IOException e) {throw new JSONException("io error", e);}
}
fillBuffer
方法采用动态扩容策略,每次将缓冲区大小翻倍,并通过reader.read
填充新数据,确保字符流的连续读取。
3. Token扫描的核心方法:scanSymbol
scanSymbol
方法是词法分析的核心逻辑,负责将字符转换为对应的Token:
private int scanSymbol(int c) {// 跳过空白字符(空格、换行、制表符等)while (Character.isWhitespace(c)) { c = next();}switch (c) {case '"': return scanString(c); // 扫描字符串case '[': return JSONToken.LITERAL_LEFT_BRACKET; // 数组开始case ']': return JSONToken.LITERAL_RIGHT_BRACKET; // 数组结束case '{': return JSONToken.LITERAL_LEFT_BRACE; // 对象开始case '}': return JSONToken.LITERAL_RIGHT_BRACE; // 对象结束case ',': return JSONToken.LITERAL_COMMA; // 逗号case ':': return JSONToken.LITERAL_COLON; // 冒号case 'n': if (scanSymbol("null", 4)) { // 识别"null"字符串return JSONToken.LITERAL_NULL;}break;case 't':if (scanSymbol("true", 4)) { // 识别"true"字符串return JSONToken.LITERAL_TRUE;}break;case 'f':if (scanSymbol("false", 5)) { // 识别"false"字符串return JSONToken.LITERAL_FALSE;}break;case '-': case '0': case '1': case '2': case '3':case '4': case '5': case '6': case '7': case '8': case '9':return scanNumber(c); // 扫描数字}// 遇到非法字符,抛出解析异常throw new JSONException("syntax error, unexpected character: " + (char) c);
}
该方法首先跳过空白字符,然后根据当前字符进行类型判断:遇到"
调用scanString
解析字符串,遇到数字调用scanNumber
解析数值,通过字符串匹配识别null
、true
、false
等关键字。
四、字符串Token的解析逻辑
1. scanString方法的实现
scanString
方法专门处理JSON字符串的解析,其核心难点在于转义字符的处理:
private int scanString(int c) {StringBuilder sb = new StringBuilder();boolean escaped = false;for (;;) {c = next();if (escaped) {// 处理转义字符if (c == '"') sb.append('"'); else if (c == '\\') sb.append('\\');else if (c == '/') sb.append('/');else if (c == 'b') sb.append('\b');else if (c == 'f') sb.append('\f');else if (c == 'n') sb.append('\n');else if (c == 'r') sb.append('\r');else if (c == 't') sb.append('\t');else if (c >= '0' && c <= '9') {// 处理Unicode转义字符(如\uXXXX)int u = parseUnicode(c); sb.append((char) u);}escaped = false;} else if (c == '"') {// 字符串结束text = sb.toString(); return JSONToken.LITERAL_STRING; } else if (c == '\\') {escaped = true; // 标记下一个字符为转义字符} else if (c == 0) {// 遇到非法字符(输入流提前结束)throw new JSONException("unclosed string"); } else {sb.append((char) c); // 普通字符直接添加到字符串}}
}
该方法通过escaped
标志位判断是否处于转义状态,对"
、\
、/
等特殊转义字符以及Unicode转义序列(\uXXXX
)进行解析,最终将完整字符串存储到text
变量并返回LITERAL_STRING
类型的Token。
2. Unicode转义字符的解析
parseUnicode
方法用于处理Unicode转义序列,将\uXXXX
格式的字符转换为实际的Unicode字符:
private int parseUnicode(int c) {int u = 0;for (int i = 0; i < 4; i++) {c = next();if (c >= '0' && c <= '9') {u = u * 16 + (c - '0');} else if (c >= 'a' && c <= 'f') {u = u * 16 + (c - 'a' + 10);} else if (c >= 'A' && c <= 'F') {u = u * 16 + (c - 'A' + 10);} else {throw new JSONException("invalid unicode escape sequence");}}return u;
}
该方法按顺序读取4个十六进制字符,将其转换为对应的Unicode码点,确保字符串中特殊字符的正确解析。
五、数字Token的解析逻辑
1. scanNumber方法的实现
scanNumber
方法负责解析JSON中的数字,支持整数、浮点数和科学计数法:
private int scanNumber(int c) {StringBuilder sb = new StringBuilder();boolean isFloat = false;boolean hasExponent = false;boolean allowSign = false;for (;;) {if (c >= '0' && c <= '9') {sb.append((char) c);} else if (c == '.' &&!isFloat &&!hasExponent) {sb.append('.');isFloat = true;} else if ((c == 'e' || c == 'E') &&!hasExponent) {sb.append((char) c);hasExponent = true;allowSign = true;} else if ((c == '-' || c == '+') && allowSign) {sb.append((char) c);allowSign = false;} else {break;}c = next();}text = sb.toString();// 根据数字类型返回不同的Tokenif (isFloat) { return JSONToken.LITERAL_DOUBLE; } else {return JSONToken.LITERAL_INT; }
}
该方法通过状态标记(isFloat
、hasExponent
)识别浮点数和科学计数法,在解析完成后根据数字类型返回LITERAL_DOUBLE
或LITERAL_INT
类型的Token。
2. 数字格式的合法性校验
在scanNumber
方法中,通过条件判断确保数字格式符合JSON规范:
- 浮点数只能包含一个
.
- 科学计数法只能出现一次
e
或E
- 指数部分的正负号只能出现在
e
或E
之后
一旦检测到非法格式(如多个小数点、无效的指数符号),将停止解析并抛出JSONException
异常。
六、关键字Token的解析逻辑
1. 布尔值与null的识别
scanSymbol
方法通过字符串匹配识别null
、true
、false
关键字:
private boolean scanSymbol(String expect, int length) {StringBuilder sb = new StringBuilder();for (int i = 0; i < length; i++) {int c = next();sb.append((char) c);if (c != expect.charAt(i)) {// 匹配失败,回退字符位置for (int j = 0; j < i; j++) { bufferPos--;}return false;}}text = sb.toString();return true;
}
scanSymbol
方法逐个字符对比输入与目标关键字,若匹配失败则通过bufferPos--
回退字符位置,确保后续解析不受影响。
2. 字符串匹配的性能优化
为减少不必要的字符串比较,FastJson采用前缀优先匹配策略。例如,在解析null
时,先判断首字符是否为n
,若不匹配则直接跳过后续字符比较,避免完整字符串的遍历,提升解析效率。
七、词法分析的状态机模型
1. 有限状态自动机(FSA)的应用
FastJson的词法分析本质上是一个有限状态自动机,其状态转移基于当前字符输入:
- 初始状态:等待读取下一个有效字符
- 字符串状态:读取字符串内容,遇到
"
或输入结束时转移 - 数字状态:读取数字字符,遇到非数字字符时转移
- 关键字状态:读取关键字字符,匹配成功或失败时转移
2. 状态转移的源码实现
以字符串解析为例,scanString
方法中的状态转移逻辑如下:
for (;;) {c = next();if (escaped) {// 处理转义字符,保持字符串状态} else if (c == '"') {// 遇到结束符,转移到初始状态text = sb.toString(); return JSONToken.LITERAL_STRING; } else if (c == '\\') {// 遇到转义符,进入转义状态escaped = true; } else if (c == 0) {// 输入结束,抛出异常throw new JSONException("unclosed string"); } else {// 普通字符,保持字符串状态sb.append((char) c); }
}
通过这种状态转移机制,词法分析器能够准确识别各类JSON元素,避免解析逻辑的混乱。
八、错误处理与容错机制
1. 非法字符的检测
在scanSymbol
和各子解析方法中,一旦遇到不符合JSON规范的字符,立即抛出JSONException
:
// scanSymbol方法中的异常处理
throw new JSONException("syntax error, unexpected character: " + (char) c); // scanString方法中的异常处理
throw new JSONException("unclosed string");
这些异常信息包含错误字符和位置,方便开发者定位问题。
2. 容错模式的支持
FastJson通过配置项(如Feature.AllowUnQuotedFieldNames
)支持宽松解析模式。在该模式下,词法分析器对非标准格式(如未加引号的字段名)进行容错处理,通过额外的字符匹配逻辑尝试继续解析,提升对非严格JSON数据的兼容性。
九、Android平台的适配优化
1. 内存占用优化
- 缓冲区复用:
JSONReader
的字符缓冲区采用动态扩容策略,避免一次性分配过大内存,同时减少频繁的内存分配与回收开销。 - 对象复用:在Android多线程环境下,通过对象池技术复用
JSONReader
实例,降低内存压力。
2. 性能优化
- 减少字符串操作:在解析过程中,尽量避免字符串拼接,通过
StringBuilder
和字符数组操作减少内存拷贝。 - 指令优化:针对ARM架构的Android设备,通过精简条件判断和循环逻辑,减少CPU指令周期。
十、词法分析器的扩展与定制
1. 自定义Token类型
开发者可通过继承JSONReader
并重写scanSymbol
等方法,添加自定义Token类型。例如,在解析特定领域的JSON扩展格式时,可新增自定义关键字或数据类型的解析逻辑。
2. 解析策略定制
通过实现自定义的JSONReader
子类,可调整词法分析的行为:
- 改变缓冲区大小和扩容策略
- 自定义转义字符处理规则
- 调整关键字匹配逻辑
这种扩展性使得FastJson能够适应多样化的JSON解析需求,尤其在Android应用的个性化开发场景中发挥重要作用。
通过对FastJson词法分析器的源码级拆解,从Token定义到状态机实现,再到Android平台的优化细节,完整揭示了其高效解析JSON数据的底层原理。这些设计不仅保证了基础功能