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根据以下规则区分整数和浮点数:
- 如果数字中包含小数点(
.
),则判定为浮点数 - 如果数字中包含指数部分(
e
或E
),则判定为浮点数
例如,解析123
时,isFloat
为false
,返回LITERAL_INT
;解析123.45
时,isFloat
为true
,返回LITERAL_DOUBLE
;解析1.23e4
时,hasExponent
为true
,同样返回LITERAL_DOUBLE
。
3. 科学计数法的处理
当遇到e
或E
字符时,FastJson会将其识别为科学计数法的标志,并允许后续出现可选的正负号和指数部分。例如,对于字符串1.23e-4
,解析过程如下:
- 读取
1
、.
、2
、3
,设置isFloat
为true
- 读取
e
,设置hasExponent
为true
,allowSign
为true
- 读取
-
,设置allowSign
为false
- 读取
4
,完成数字解析 - 最终返回
LITERAL_DOUBLE
类型的Token
这种机制确保了科学计数法表示的数字能够被正确识别和解析。
四、布尔值类型的识别机制
1. 布尔值的识别流程
FastJson对布尔值的识别基于关键字匹配。在JSONReader
类的scanSymbol
方法中,当遇到字符t
、f
时,会尝试匹配true
和false
关键字:
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
:
- 读取字符
t
,与true
的第一个字符匹配 - 读取字符
r
,与true
的第二个字符匹配 - 读取字符
u
,与true
的第三个字符匹配 - 读取字符
e
,与true
的第四个字符匹配 - 全部匹配成功,返回
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
:
- 读取字符
n
,与null
的第一个字符匹配 - 读取字符
u
,与null
的第二个字符匹配 - 读取字符
l
,与null
的第三个字符匹配 - 读取字符
l
,与null
的第四个字符匹配 - 全部匹配成功,返回
LITERAL_NULL
3. null值识别的注意事项
在JSON中,null
是一个关键字,必须全部小写。FastJson严格遵循这一规范,只有遇到完全匹配null
的字符串时,才会识别为null值。如果遇到类似Null
、NULL
等变体,会被视为非法字符,抛出解析异常。
六、对象类型的识别机制
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的键值对格式:
- 读取属性名(必须是字符串类型)
- 读取冒号(
:
)分隔符 - 读取属性值(可以是任意JSON类型)
- 读取逗号(
,
)分隔符或右大括号(}
)
例如,对于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的数组格式:
- 读取数组元素(可以是任意JSON类型)
- 读取逗号(
,
)分隔符或右方括号(]
)
例如,对于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的解析流程如下:
- 读取左大括号(
{
),开始解析外层对象 - 读取字符串
"name"
,作为属性名 - 读取冒号(
:
) - 读取字符串
"John"
,作为属性值 - 读取逗号(
,
) - 读取字符串
"age"
,作为属性名 - 读取冒号(
:
) - 读取数字
30
,作为属性值 - 读取逗号(
,
) - 读取字符串
"address"
,作为属性名 - 读取冒号(
:
) - 读取左大括号(
{
),递归解析嵌套对象 - 解析嵌套对象的属性(
street
、city
、zip
) - 读取右大括号(
}
),结束嵌套对象解析 - 读取逗号(
,
) - 读取字符串
"hobbies"
,作为属性名 - 读取冒号(
:
) - 读取左方括号(
[
),递归解析嵌套数组 - 解析数组元素(
reading
、running
、swimming
) - 读取右方括号(
]
),结束嵌套数组解析 - 读取右大括号(
}
),结束外层对象解析
2. 混合类型的处理
FastJson能够处理包含多种数据类型的复杂结构。例如,对于以下JSON数组:
[123,"hello",{"key": "value"},[true, false],null
]
FastJson会依次识别并处理每种数据类型:
- 数字
123
- 字符串
"hello"
- 嵌套对象
{"key": "value"}
- 嵌套数组
[true, false]
- 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特有的数据类型:如
Color
、Dimension
等。 - Android资源引用:如
@string/
、@drawable/
等。 - JSONP支持:处理JSON with Padding格式的数据。
这些优化和扩展使得FastJson在Android平台上能够更好地满足移动应用开发的需求。