开源工程学习笔记之Fastjson(Part 1)

Part 1.反序列化部分

还是从part0那个json串入手,看看Fastjson框架是怎么把他拆出来的。

1.1 Fastjson中存取JSON对象的方法

首先,Fastjson是用JSONObject和JSONArray这两个类分别描述json对象和json数组的。那么先把这两个类拆开来看看。

1.1.1 JSONObject

通过阅读json文档得知,一个object由若干个(可以是0个)键值对组成,其中键是字符串(string),值可以是string,number,object,array,boolean,null这几种情况。那么这种数据结构在Java中对应了什么呢?

没错就是Map啊!~~

所以JSONObject封装了一个private final Map map;,那么这个map是什么map呢?这取决于构造器了,默认的构造器是:

public JSONObject(){
    this(DEFAULT_INITIAL_CAPACITY, false);
}

第一个参数代表初始容量,程序设定为16,第二个参数代表是否有序,默认为false。

如果有序,则用LinkedHashMap实现,否则用HashMap实现。

既然这样,那么JSON对象的key就不可以是重复的~~之前拆的cJSON是可以重复的,个人感觉就不对了。因为json文档中说

An object is an unordered set of name/value pairs.

既然是set,就不能重复的吧。。。

这个类的方法都比较简单,大多是HashMap的封装,get和set这种的。首先是各种根据key取对象的方法,有取Object的,取Array的,取JavaBean的等等。

public JSONObject getJSONObject(String key) {
    Object value = map.get(key);

    if (value instanceof JSONObject) {
        return (JSONObject) value;
    }

    if (value instanceof String) {
        return JSON.parseObject((String) value);
    }

    return (JSONObject) toJSON(value);
}

取对象的逻辑很简单,就是如果他是对应的对象,则直接取出来,如果是字符串,则parse成对象,否则转换成JSONObject(如果是JavaBean)。

此外,该框架还支持直接取日期、取时间戳、取BigInteger等封装好的方法,在底层均是调用了com.alibaba.fastjson.util包里的工具方法,在此先不分析。

然后添加元素方面呢,有一个比较有意思的fluentPut:

public JSONObject fluentPut(String key, Object value) {
    map.put(key, value);
    return this;
}

因为他返回的是this,所以就可以在一行代码里连续加入多个元素:(记得以前看php的时候看到连续调用觉得特别方便,其实实现原理都是一样的。)

myObj.fluentPut("key1",value1).fluentPut("key2",value2);

1.1.2 JSONArray

JSONArray和JSONObject差不多,就是底层结构换成了ArrayList。

1.1.3 TypeUtils工具包中直接取特殊类型的方法

前面说过,这个框架支持将json中的value直接转换成各种数字类、高精度、日期、时间戳等数据结构,都是在TypeUtils包中的castToxxx方法实现的。

  • 基本类型

castToString、Byte、Char、Short、BigInteger、BigDecimal、Float、Double等都比较简单,主要就是特殊处理了value为”“,”NULL”,”null”三种情况,如果处理不了,就抛出JSONException异常。

  • Date

关键是Date,可见该框架支持了很多很多日期类型,首先调用了词法分析器JSONScanner,先检测是否符合ISO 8601标准,再检测是否是”yyyy-MM-dd HH:mm:ss”,”yyyy-MM-dd”,”yyyy-MM-dd HH:mm:ss.SSS”等形式。

else if (strVal.length() == 10) {
                    format = "yyyy-MM-dd";
                } else if (strVal.length() == "yyyy-MM-dd HH:mm:ss".length()) {
                    format = "yyyy-MM-dd HH:mm:ss";
                }

顺便说下,这两句话风格统一下好不好。。。。

最后如果都不是,则看是不是时间戳,如果是时间戳则转换为对应的日期。
* JavaBean
还有一种特殊情况,就是取指定类,这个就比较麻烦了,如果是一些简单类如Integer,Float等直接调用相关方法即可,或者value存的就是你指定的类,都比较简单。

最复杂的情况就是,JSONObject里面存的是一个Map,而要取出的是这个Map对应的Bean。这个逻辑在方法castToJavaBean里实现。

1.2 输入字符串的反序列化

反序列化分为两部分,一是转换为JSON对象,二是转换为JavaBean。

1.2.1 String转换为JSONObject或JSONArray

了解了JSON对象在框架内存储的结构之后,我们从输入字符串开始,对解析流程进行分析。

public static Object parse(String text) {
    return parse(text, DEFAULT_PARSER_FEATURE);
}

这是第一层接口,然后再调用重载函数:

public static Object parse(String text, int features) {
    if (text == null) {
        return null;
    }

    DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
    Object value = parser.parse();

    parser.handleResovleTask(value);

    parser.close();

    return value;
}

我们发现,字符串的处理工作是在DefaultJSONParser类进行的,程序将text字符串传入,并使用默认配置和默认特征。(配置项都是什么东西,先不管了,估计也看不懂。。。)

public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){
       this.lexer = lexer;
       this.input = input;
       this.config = config;
       this.symbolTable = config.symbolTable;

       int ch = lexer.getCurrent();
       if (ch == '{') {
           lexer.next();
           ((JSONLexerBase) lexer).token = JSONToken.LBRACE;
       } else if (ch == '[') {
           lexer.next();
           ((JSONLexerBase) lexer).token = JSONToken.LBRACKET;
       } else {
           lexer.nextToken(); // prime the pump
       }
   }

观察这个东西的构造器发现,解析字符串的工作需要接受三个参数,分别是输入的字符串,词法分析器lexer,和配置项Config。如果不传参数,均使用默认设置。

词法分析器的实现是com.alibaba.fastjson.parser包中的JSONScanner类,这个类有1300多行代码,是该模块的核心部分。

有个需要注意的地方,就是程序在构造的时候就先判断了第一个符号,并让lexer转换了状态。这应该是一种提高性能的办法吧~~~

接下来执行Object value = parser.parse();开始分析字符串。

程序在JSONToken类中定义了以下符号,供词法分析器使用(分别代表词法分析器的状态):

public final static int ERROR                = 1;
public final static int LITERAL_INT          = 2;
public final static int LITERAL_FLOAT        = 3;
public final static int LITERAL_STRING       = 4;
public final static int LITERAL_ISO8601_DATE = 5;
public final static int TRUE                 = 6;
public final static int FALSE                = 7;
public final static int NULL                 = 8;
public final static int NEW                  = 9;
public final static int LPAREN               = 10; // ("("),
public final static int RPAREN               = 11; // (")"),
public final static int LBRACE               = 12; // ("{"),
public final static int RBRACE               = 13; // ("}"),
public final static int LBRACKET             = 14; // ("["),
public final static int RBRACKET             = 15; // ("]"),
public final static int COMMA                = 16; // (","),
public final static int COLON                = 17; // (":"),
public final static int IDENTIFIER           = 18;
public final static int FIELD_NAME           = 19;
public final static int EOF                  = 20;
public final static int SET                  = 21;
public final static int TREE_SET             = 22;
public final static int UNDEFINED            = 23; // undefined

以下是parse的源码:

public Object parse(Object fieldName) {
    final JSONLexer lexer = this.lexer;
    switch (lexer.token()) {
        case SET:
            lexer.nextToken();
            HashSet<Object> set = new HashSet<Object>();
            parseArray(set, fieldName);
            return set;
        case TREE_SET:
            lexer.nextToken();
            TreeSet<Object> treeSet = new TreeSet<Object>();
            parseArray(treeSet, fieldName);
            return treeSet;
        case LBRACKET:
            JSONArray array = new JSONArray();
            parseArray(array, fieldName);
            if (lexer.isEnabled(Feature.UseObjectArray)) {
                return array.toArray();
            }
            return array;
        case LBRACE:
            JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
            return parseObject(object, fieldName);
        case LITERAL_INT:
            Number intValue = lexer.integerValue();
            lexer.nextToken();
            return intValue;
        case LITERAL_FLOAT:
            Object value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal));
            lexer.nextToken();
            return value;
        case LITERAL_STRING:
            String stringLiteral = lexer.stringVal();
            lexer.nextToken(JSONToken.COMMA);

            if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
                JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
                try {
                    if (iso8601Lexer.scanISO8601DateIfMatch()) {
                        return iso8601Lexer.getCalendar().getTime();
                    }
                } finally {
                    iso8601Lexer.close();
                }
            }

            return stringLiteral;
        case NULL:
            lexer.nextToken();
            return null;
        case UNDEFINED:
            lexer.nextToken();
            return null;
        case TRUE:
            lexer.nextToken();
            return Boolean.TRUE;
        case FALSE:
            lexer.nextToken();
            return Boolean.FALSE;
        case NEW:
            lexer.nextToken(JSONToken.IDENTIFIER);

            if (lexer.token() != JSONToken.IDENTIFIER) {
                throw new JSONException("syntax error");
            }
            lexer.nextToken(JSONToken.LPAREN);

            accept(JSONToken.LPAREN);
            long time = ((Number) lexer.integerValue()).longValue();
            accept(JSONToken.LITERAL_INT);

            accept(JSONToken.RPAREN);

            return new Date(time);
        case EOF:
            if (lexer.isBlankInput()) {
                return null;
            }
            throw new JSONException("unterminated json string, " + lexer.info());
        case ERROR:
        default:
            throw new JSONException("syntax error, " + lexer.info());
    }
}

观察parse函数的源码不难发现,其实核心算法就是一个自动机转换,那么接下来分析各个状态。

SET、TREE_SET:不明

LBRACKET:说明词法分析器遇到了’[‘,这表明是一个数组的开始,所以new了一个JSONArray对象,再调用parseArray。
parseArray函数首先让词法分析器沿着字符串寻找,找到’LITERAL_STRING’状态,大概就是遇到了有效字符(非空、换行之类),然后从LITERAL_STRING开始读,读完第一个元素:

case JSONToken.LITERAL_STRING:
    if (ch == '"') {
        pos = bp;
        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;

这时候可能会遇到字符串、数字、[、{、分别对应不同的状态,如果是字符串或数字,则调用scanString或scanNumber将对应字符存进一个缓冲区,并把状态切换到LITERAL_INT或者LITERAL_FLOAT或者LITERAL_STRING.

switch (lexer.token()) {
    case LITERAL_INT:
        value = lexer.integerValue();
        lexer.nextToken(JSONToken.COMMA);
        break;
    case LITERAL_FLOAT:
        if (lexer.isEnabled(Feature.UseBigDecimal)) {
            value = lexer.decimalValue(true);
        } else {
            value = lexer.decimalValue(false);
        }
        lexer.nextToken(JSONToken.COMMA);
        break;
    case LITERAL_STRING:
        String stringLiteral = lexer.stringVal();
        lexer.nextToken(JSONToken.COMMA);

        if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
            JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
            if (iso8601Lexer.scanISO8601DateIfMatch()) {
                value = iso8601Lexer.getCalendar().getTime();
            } else {
                value = stringLiteral;
            }
            iso8601Lexer.close();
        } else {
            value = stringLiteral;
        }

        break;

接下来进入这一段,获取字符串/数字的值,其中用到了三个配置项:
(1)Feature.AllowArbitraryCommas:允许多重逗号,如果设为true,则遇到多个逗号会直接跳过;
(2)Feature.UseBigDecimal:这个设置为true则用BigDecimal类来装载数字,否则用的是double;
(3)Feature.AllowISO8601DateFormat:这个设置为true则遇到字符串符合ISO8601格式的日期时,会直接转换成日期类。

case LBRACE:
    JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
    value = parseObject(object, i);
    break;
case LBRACKET:
    Collection items = new JSONArray();
    parseArray(items, i);
    if (lexer.isEnabled(Feature.UseObjectArray)) {
        value = items.toArray();
    } else {
        value = items;
    }
    break;

若是遇到左括号,则递归调用下去,继续解析对象或数组。

接下来调用lexer.nextToken(JSONToken.COMMA);,寻找一个逗号。找到逗号后,执行这一段:

array.add(value); //把value加入JSONarray中
checkListResolve(array);//不明

if (lexer.token() == JSONToken.COMMA) { //再找下一个LITERAL_STRING
    lexer.nextToken(JSONToken.LITERAL_STRING);
    continue;
}

接着加入下一个元素,直到遇到右方括号。

case RBRACKET:
    lexer.nextToken(JSONToken.COMMA);
    return;

可以看到程序return出来了。

接着回到上面,如果遇到左大括号,首先new了一个JSONObject类,使用Feature.OrderedField配置项来控制是否有序,也就是用LinkedHashMap实现还是用HashMap实现,接下来我们就进入了parseObject方法。

这两个方法八成不是一个人写的,思路完全不一样了~~

既然是Object,那就是带key的, 首先要找key,发现这个框架是支持用数字做key的~这个parseObject函数真的是非常复杂,有近400行长,我实在是看不明白了。。。。

剩下的几个状态就是该json只有一个值,比如只有一个数等等的情况。直接返回就好了~~

今天就琢磨到这,下次争取深入探讨fastjson对字符串的处理。

你可能感兴趣的:(Java)