Android FastJson不同JSON数据类型的识别机制原理深度剖析

一、FastJson中JSON数据类型识别的整体架构

在FastJson解析JSON数据的过程中,准确识别不同的数据类型是构建正确对象模型的基础。FastJson通过词法分析器(Lexer)和语法分析器协同工作,实现对JSON数据类型的识别。词法分析器负责将输入的JSON字符流转换为一个个独立的词法单元(Token),而语法分析器则根据这些Token构建出对应的Java对象。在Android平台上,这种识别机制需要高效且轻量,以适应移动设备的资源限制。FastJson通过精心设计的状态机和类型推断算法,能够快速准确地识别JSON中的各种数据类型,包括字符串、数字、布尔值、对象、数组和null值等。

二、字符串类型的识别机制

1. 字符串识别的基本流程

FastJson对字符串类型的识别始于词法分析阶段,当遇到双引号字符(")时,会触发字符串解析逻辑。在JSONReader类的scanString方法中,实现了字符串的完整解析过程:

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标志来处理转义字符,确保转义序列(如\"\\)被正确解析为单个字符。

2. 转义字符的处理细节

当遇到反斜杠字符(\)时,escaped标志被设置为true,表示下一个字符需要特殊处理。FastJson支持多种转义字符,包括:

  • \":双引号
  • \\:反斜杠
  • \/:斜杠
  • \b:退格符
  • \f:换页符
  • \n:换行符
  • \r:回车符
  • \t:制表符
  • \uXXXX:Unicode字符

对于\uXXXX格式的Unicode转义字符,FastJson通过parseUnicode方法进行解析:

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;
}

该方法读取接下来的四个字符,将其转换为对应的Unicode码点。如果遇到非十六进制字符,会抛出异常,确保Unicode转义序列的合法性。

3. 字符串识别的性能优化

在Android平台上,字符串操作可能会带来较大的内存开销。FastJson通过以下方式优化字符串识别的性能:

  • 使用StringBuilder:在构建字符串时,使用StringBuilder而非直接拼接字符串,减少中间对象的创建。
  • 字符直接处理:在缓冲区中直接处理字符,避免频繁的字符数组转换,提高处理速度。
  • 边界检查优化:在读取字符时,会先检查缓冲区是否足够,不足时自动扩容,减少内存分配次数。

三、数字类型的识别机制

1. 数字类型的分类与识别流程

FastJson能够识别多种数字类型,包括整数、浮点数和科学计数法表示的数字。在JSONReader类的scanNumber方法中,实现了数字的解析逻辑:

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();if (isFloat) { return JSONToken.LITERAL_DOUBLE; } else {return JSONToken.LITERAL_INT; }
}

该方法通过多个标志变量来跟踪数字的类型和状态:

  • isFloat:标记是否为浮点数
  • hasExponent:标记是否包含指数部分
  • allowSign:标记是否允许指数部分的符号

2. 整数与浮点数的区分

在解析过程中,FastJson根据以下规则区分整数和浮点数:

  • 如果数字中包含小数点(.),则判定为浮点数
  • 如果数字中包含指数部分(eE),则判定为浮点数

例如,解析123时,isFloatfalse,返回LITERAL_INT;解析123.45时,isFloattrue,返回LITERAL_DOUBLE;解析1.23e4时,hasExponenttrue,同样返回LITERAL_DOUBLE

3. 科学计数法的处理

当遇到eE字符时,FastJson会将其识别为科学计数法的标志,并允许后续出现可选的正负号和指数部分。例如,对于字符串1.23e-4,解析过程如下:

  1. 读取1.23,设置isFloattrue
  2. 读取e,设置hasExponenttrueallowSigntrue
  3. 读取-,设置allowSignfalse
  4. 读取4,完成数字解析
  5. 最终返回LITERAL_DOUBLE类型的Token

这种机制确保了科学计数法表示的数字能够被正确识别和解析。

四、布尔值类型的识别机制

1. 布尔值的识别流程

FastJson对布尔值的识别基于关键字匹配。在JSONReader类的scanSymbol方法中,当遇到字符tf时,会尝试匹配truefalse关键字:

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)) {return JSONToken.LITERAL_NULL;}break;case 't':if (scanSymbol("true", 4)) {return JSONToken.LITERAL_TRUE;}break;case 'f':if (scanSymbol("false", 5)) {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);
}

2. 关键字匹配的实现

关键字匹配由scanSymbol方法的重载版本实现:

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;
}

当遇到字符t时,会尝试匹配true

  1. 读取字符t,与true的第一个字符匹配
  2. 读取字符r,与true的第二个字符匹配
  3. 读取字符u,与true的第三个字符匹配
  4. 读取字符e,与true的第四个字符匹配
  5. 全部匹配成功,返回LITERAL_TRUE

如果匹配过程中任何一个字符不匹配,会回退字符位置,继续扫描后续字符。

3. 布尔值识别的性能优化

为提高布尔值识别的效率,FastJson采用了前缀优先匹配策略:

  • 遇到t时,优先尝试匹配true
  • 遇到f时,优先尝试匹配false

这种策略避免了对所有可能的关键字进行逐一检查,减少了不必要的字符比较操作。在处理大量JSON数据时,这种优化能够显著降低CPU开销,提高解析速度。

五、null值的识别机制

1. null值的识别流程

FastJson对null值的识别同样基于关键字匹配。在JSONReader类的scanSymbol方法中,当遇到字符n时,会尝试匹配null关键字:

case 'n': if (scanSymbol("null", 4)) {return JSONToken.LITERAL_NULL;}break;

2. null关键字的匹配实现

null关键字的匹配逻辑与布尔值类似,由scanSymbol方法实现:

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;
}

当遇到字符n时,会尝试匹配null

  1. 读取字符n,与null的第一个字符匹配
  2. 读取字符u,与null的第二个字符匹配
  3. 读取字符l,与null的第三个字符匹配
  4. 读取字符l,与null的第四个字符匹配
  5. 全部匹配成功,返回LITERAL_NULL

3. null值识别的注意事项

在JSON中,null是一个关键字,必须全部小写。FastJson严格遵循这一规范,只有遇到完全匹配null的字符串时,才会识别为null值。如果遇到类似NullNULL等变体,会被视为非法字符,抛出解析异常。

六、对象类型的识别机制

1. 对象识别的起始与结束

FastJson对JSON对象的识别始于左大括号({),终于右大括号(})。在JSONReader类的parseObject方法中,实现了对象的解析逻辑:

private Object parseObject() {token = nextToken(); // 读取下一个Tokenif (token == JSONToken.RBRACE) { // 空对象return new JSONObject();}JSONObject object = new JSONObject();for (;;) {if (token == JSONToken.LITERAL_STRING) {String key = text;token = nextToken();if (token != JSONToken.COLON) {throw new JSONException("expect ':' at " + pos);}token = nextToken();Object value = parseValue();object.put(key, value);} else {throw new JSONException("expect a string key at " + pos);}token = nextToken();if (token == JSONToken.RBRACE) {break;}if (token != JSONToken.COMMA) {throw new JSONException("expect ',' at " + pos);}token = nextToken();}return object;
}

2. 对象属性的解析

在解析对象时,FastJson遵循JSON的键值对格式:

  1. 读取属性名(必须是字符串类型)
  2. 读取冒号(:)分隔符
  3. 读取属性值(可以是任意JSON类型)
  4. 读取逗号(,)分隔符或右大括号(}

例如,对于JSON对象{"name": "John", "age": 30},解析过程如下:

  • 读取左大括号({),开始解析对象
  • 读取字符串"name",作为属性名
  • 读取冒号(:
  • 读取字符串"John",作为属性值
  • 读取逗号(,
  • 读取字符串"age",作为下一个属性名
  • 读取冒号(:
  • 读取数字30,作为属性值
  • 读取右大括号(}),结束对象解析

3. 对象嵌套的处理

FastJson能够递归处理嵌套的JSON对象。当解析属性值时,如果遇到左大括号({),会递归调用parseObject方法:

private Object parseValue() {switch (token) {case JSONToken.LITERAL_STRING:return text;case JSONToken.LITERAL_INT:return Long.parseLong(text);case JSONToken.LITERAL_DOUBLE:return Double.parseDouble(text);case JSONToken.LITERAL_TRUE:return Boolean.TRUE;case JSONToken.LITERAL_FALSE:return Boolean.FALSE;case JSONToken.LITERAL_NULL:return null;case JSONToken.LBRACE:return parseObject(); // 递归解析嵌套对象case JSONToken.LBRACKET:return parseArray();default:throw new JSONException("syntax error, token: " + token);}
}

这种递归机制确保了无论JSON对象嵌套有多深,FastJson都能正确解析。

七、数组类型的识别机制

1. 数组识别的起始与结束

FastJson对JSON数组的识别始于左方括号([),终于右方括号(])。在JSONReader类的parseArray方法中,实现了数组的解析逻辑:

private Object parseArray() {token = nextToken(); // 读取下一个Tokenif (token == JSONToken.RBRACKET) { // 空数组return new JSONArray();}JSONArray array = new JSONArray();for (;;) {Object value = parseValue();array.add(value);token = nextToken();if (token == JSONToken.RBRACKET) {break;}if (token != JSONToken.COMMA) {throw new JSONException("expect ',' at " + pos);}token = nextToken();}return array;
}

2. 数组元素的解析

在解析数组时,FastJson遵循JSON的数组格式:

  1. 读取数组元素(可以是任意JSON类型)
  2. 读取逗号(,)分隔符或右方括号(]

例如,对于JSON数组[1, "hello", true, null],解析过程如下:

  • 读取左方括号([),开始解析数组
  • 读取数字1,作为第一个元素
  • 读取逗号(,
  • 读取字符串"hello",作为第二个元素
  • 读取逗号(,
  • 读取布尔值true,作为第三个元素
  • 读取逗号(,
  • 读取null值,作为第四个元素
  • 读取右方括号(]),结束数组解析

3. 数组嵌套的处理

FastJson能够递归处理嵌套的JSON数组。当解析数组元素时,如果遇到左方括号([),会递归调用parseArray方法:

private Object parseValue() {switch (token) {case JSONToken.LITERAL_STRING:return text;case JSONToken.LITERAL_INT:return Long.parseLong(text);case JSONToken.LITERAL_DOUBLE:return Double.parseDouble(text);case JSONToken.LITERAL_TRUE:return Boolean.TRUE;case JSONToken.LITERAL_FALSE:return Boolean.FALSE;case JSONToken.LITERAL_NULL:return null;case JSONToken.LBRACE:return parseObject();case JSONToken.LBRACKET:return parseArray(); // 递归解析嵌套数组default:throw new JSONException("syntax error, token: " + token);}
}

这种递归机制确保了无论JSON数组嵌套有多深,FastJson都能正确解析。

八、复杂JSON结构的识别与处理

1. 嵌套结构的解析

FastJson通过递归调用的方式处理复杂的嵌套结构。例如,对于以下JSON数据:

{"name": "John","age": 30,"address": {"street": "123 Main St","city": "New York","zip": "10001"},"hobbies": ["reading", "running", "swimming"]
}

FastJson的解析流程如下:

  1. 读取左大括号({),开始解析外层对象
  2. 读取字符串"name",作为属性名
  3. 读取冒号(:
  4. 读取字符串"John",作为属性值
  5. 读取逗号(,
  6. 读取字符串"age",作为属性名
  7. 读取冒号(:
  8. 读取数字30,作为属性值
  9. 读取逗号(,
  10. 读取字符串"address",作为属性名
  11. 读取冒号(:
  12. 读取左大括号({),递归解析嵌套对象
  13. 解析嵌套对象的属性(streetcityzip
  14. 读取右大括号(}),结束嵌套对象解析
  15. 读取逗号(,
  16. 读取字符串"hobbies",作为属性名
  17. 读取冒号(:
  18. 读取左方括号([),递归解析嵌套数组
  19. 解析数组元素(readingrunningswimming
  20. 读取右方括号(]),结束嵌套数组解析
  21. 读取右大括号(}),结束外层对象解析

2. 混合类型的处理

FastJson能够处理包含多种数据类型的复杂结构。例如,对于以下JSON数组:

[123,"hello",{"key": "value"},[true, false],null
]

FastJson会依次识别并处理每种数据类型:

  1. 数字123
  2. 字符串"hello"
  3. 嵌套对象{"key": "value"}
  4. 嵌套数组[true, false]
  5. null值

3. 大型JSON数据的解析优化

对于大型JSON数据,FastJson采用流式解析策略,避免一次性将整个数据加载到内存中。通过逐步读取和处理数据,减少了内存占用,提高了解析效率。在Android平台上,这种优化尤为重要,因为移动设备的内存资源相对有限。

九、数据类型识别的错误处理机制

1. 非法字符的处理

当遇到不符合JSON规范的字符时,FastJson会抛出JSONException。例如,在解析字符串时,如果遇到未闭合的字符串:

throw new JSONException("unclosed string");

在解析数字时,如果遇到非法的数字格式:

throw new JSONException("invalid number format");

2. 类型不匹配的处理

当遇到类型不匹配的情况时,FastJson会根据配置选择抛出异常或进行类型转换。例如,在将JSON字符串转换为Java对象时,如果JSON中的某个属性类型与Java对象的字段类型不匹配:

throw new JSONException("type mismatch, expect " + expectedType + ", but found " + actualType);

3. 错误位置的定位

FastJson在抛出异常时,会包含错误发生的位置信息,方便开发者定位问题。例如:

throw new JSONException("syntax error, unexpected character: x at position 123");

其中,position 123表示错误发生在输入流的第123个字符处。

十、Android平台上的数据类型识别优化

1. 内存使用优化

在Android平台上,内存资源相对有限。FastJson通过以下方式优化内存使用:

  • 减少对象创建:在解析过程中,尽量复用对象,减少内存分配。
  • 字符缓冲区管理:使用适当大小的字符缓冲区,避免内存浪费。
  • 延迟解析:对于大型JSON数据,采用延迟解析策略,按需解析数据。

2. 性能优化

为提高在Android设备上的解析性能,FastJson进行了以下优化:

  • 字节码优化:生成高效的字节码,减少方法调用开销。
  • 分支预测优化:调整代码结构,提高CPU分支预测命中率。
  • 并行解析:在多核CPU上,支持并行解析大型JSON数据。

3. 特殊格式支持

FastJson在Android平台上支持一些特殊的JSON格式,例如:

  • Android特有的数据类型:如ColorDimension等。
  • Android资源引用:如@string/@drawable/等。
  • JSONP支持:处理JSON with Padding格式的数据。

这些优化和扩展使得FastJson在Android平台上能够更好地满足移动应用开发的需求。