在开始正文之前,先给大家推荐一些学习技巧:
其次,说一下,整个JSON解析使用的是遍历分析(词法解析)、灵活应用堆栈来实现(语法解析),没有使用递归方式,这样就免受了JVM调用堆栈溢出问题的困饶。
先看看文件结构:
Lexical-----词法解析,就是字符串解析(主要就是解析为true、false、null、String、num这几种基本类型)。Type类,定义了JOSN的数据类型;JsonLex类就是粗略的判断了下输入数据的JSON类型和大概的值;LeptType类保存数据的类型和真实值;UnexpectedException类自定义一个捕获错误类,用于报错。
Grammar-----语法解析,就是利用栈和HashMap等数据结构来解析数组和对象这两种符合结构。STATE类,定义状态常量;StateMachine类(总指挥官)将所有状态转化都联系起来实现JSON解析器;OPT类定义操作常量;Operator类具体操作类。
主要就是解析为true、false、null、String、num这几种基本类型。
Type类:
定义‘[’ ‘]’ ‘{’ ‘}’ ‘,’ ':'等类型是为了后面状态转换用。
JsonLex类:
public LeptType leptParseValue() {
if (lineNum == 0) {
lineNum++;
return LeptType.BGN;
}
char ch;
while ((ch = nextChar()) != 0) {
startLine = this.lineNum;
startCol = getColNum();
switch (ch) {
case '[':
return LeptType.ARRS;
case ']':
return LeptType.ARRE;
case '{':
return LeptType.OBJS;
case '}':
return LeptType.OBJE;
case ',':
return LeptType.SPLIT;
case ':':
return LeptType.DESC;
case 't':
case 'f':
case 'n':
return getDefLeptType();
case '"':
return new LeptType(Type.STR, getStrValue(ch));
default:
if(isSpace(ch))continue;
if(isdigit(ch)||ch=='-') return new LeptType(Type.NUM, getNumValue(ch));
}
}
if (ch == 0) {
return LeptType.EOF;
}
return null;
}
核心就是leptParseValue() 这个方法,至于怎么判断这几个基本类型是否合法,可以去看看大牛的每一章的文档,都介绍的很详细,在这里就不多说了。
LeptType类:
public class LeptType {
public static final LeptType DESC = new LeptType(Type.DESC);
public static final LeptType SPLIT = new LeptType(Type.SPLIT);
public static final LeptType ARRS = new LeptType(Type.ARRS);
public static final LeptType OBJS = new LeptType(Type.OBJS);
public static final LeptType ARRE = new LeptType(Type.ARRE);
public static final LeptType OBJE = new LeptType(Type.OBJE);
public static final LeptType FALSE = new LeptType(Type.FALSE);
public static final LeptType TRUE = new LeptType(Type.TRUE);
public static final LeptType NULL = new LeptType(Type.NULL);
public static final LeptType BGN = new LeptType(Type.BGN);
public static final LeptType EOF = new LeptType(Type.EOF);
// 从Type类中定义的类型
private Integer type;
// 该Type的值
private String value;
public LeptType(Integer type) {
this.type = type;
}
public LeptType(Integer type, String value) {
this.type = type;
this.value = value;
}
public Integer getType() {
return type;
}
public String getValue() {
return value;
}
private String unescape(String value){
StringBuilder sb=new StringBuilder();
for(int i=0;i 1) {//出字符串和数字类型外
return "[" + Type.changeTypeToStr(this.type) + "]";
} else {
return "[" + Type.changeTypeToStr(this.type) + ":" + this.value
+ "]";
}
}
public String toLocalString() {
if (this.type > 1) {
return "“" + Type.changeTypeToLocalStr(this.type) + "”";
} else {
return "“" + Type.changeTypeToLocalStr(this.type) + ":" + this.value
+ "”";
}
}
}
这个类也很简单,就是将JsonLex类判定好的类型和值传给LeptType类来进行保存。注意除了String和num这两个类型之外,其他类型的保存方式是直接用类型保存的,所以都定义成了对象常量(直接赋值就可以,很简便),String和num这两个类型,特别定义 private Integer type; private String value;这两个私有属性来保存。
到此词法解析就结束了,很简单,难点在于语法解析。
先来看张图
只要能看懂图,就已经大概理解实现原理啦~
然后来看看Status类:
public class Status {
//开始解析状态
public static final Integer BGN = 0;
//数组值前态
public static final Integer ARRBV = 1;
//数组值后态
public static final Integer ARRAV = 2;
//对象键前态
public static final Integer OBJBK = 3;
//对象键后态
public static final Integer OBJAK = 4;
//对象值前态
public static final Integer OBJBV = 5;
//对象值后态
public static final Integer OBJAV = 6;
//结果态
public static final Integer VAL = 7;
//结束态
public static final Integer EOF = 8;
//错误态
public static final Integer ERR = 9;
//状态机的状态转换矩阵
public static final Integer[][] STM = {
/*INPUT——STR NUM DESC SPLIT ARRS OBJS ARRE OBJE FALSE TRUE NULL BGN*/
/*BGN*/ {VAL,VAL,ERR,ERR,ARRBV,OBJBK,ERR,ERR,VAL,VAL,VAL,BGN},
/*ARRBV*/{ARRAV,ARRAV,ERR,ERR,ARRBV,OBJBK,VAL,ERR,ARRAV,ARRAV,ARRAV,ERR},
/*ARRAV*/{ERR,ERR,ERR,ARRBV,ERR,ERR,VAL,ERR,ERR,ERR,ERR,ERR},
/*OBJBK*/{OBJAK,ERR,ERR,ERR,ERR,ERR,ERR,VAL,ERR,ERR,ERR,ERR},//JSON对象的键只能是JSON字符串,值可以任意
/*OBJAK*/{ERR,ERR,OBJBV,ERR,ERR,ERR,ERR,ERR,ERR,ERR,ERR,ERR},
/*OBJBV*/{OBJAV,OBJAV,ERR,ERR,ARRBV,OBJBK,ERR,ERR,OBJAV,OBJAV,OBJAV,ERR},
/*OBJAV*/{ERR,ERR,ERR,OBJBK,ERR,ERR,ERR,VAL,ERR,ERR,ERR,ERR},
/*VAL*/{},//没有后续状态
/*EOF*/{},//没有后续状态
/*ERR*/{}//没有后续状态
};
//LeptType输入操作列表:当输入为这个类型的LeptType时,我要执行一个什么操作
/*INPUT —— STR NUM DESC SPLIT ARRS OBJS ARRE OBJE FALSE TRUE NIL BGN*/
public static final Method[] LtInput={
null,null,null,null,OPT.ARRS,OPT.OBJS,null,null,null,null,null,null //主要是利用动态代理来调用Operator类中的arrs()和objs()方法,分配数组和图
};
//目标状态转换操作列表:如果转换为这个状态的话,我会执行什么操作。
/*TO:BGN ARRBV ARRAV OBJBK OBJAK OBJBV OBJAV VAL EOF ERR*/
public static final Method[] LtGoal={
null,null,OPT.ARRAV,null,OPT.OBJAK,null,OPT.OBJAV,OPT.VAL,null,null
};
//期望LeptType描述列表
/*FROM:BGN ARRBV ARRAV OBJBK OBJAK OBJBV OBJAV VAL EOF ERR*/
public static final String[] ETS = {
getExpectStr(BGN), getExpectStr(ARRBV), getExpectStr(ARRAV), getExpectStr(OBJBK), getExpectStr(OBJAK), getExpectStr(OBJBV), getExpectStr(OBJAV),Type.changeTypeToLocalStr(Type.EOF),Type.changeTypeToLocalStr(Type.EOF),Type.changeTypeToLocalStr(Type.EOF)
};
//状态描述列表
/*BGN ARRBV ARRAV OBJBK OBJAK OBJBV OBJAV VAL EOF ERR*/
public static final String[] STS = {
"解析开始","数组待值","数组得值","对象待键","对象得键","对象待值","对象得值","得最终值","解析结束","异常错误"
};
//将状态数值转换为状态描述
public static String castLocalStr(Integer s){
return STS[s];
}
//获取期望Token描述字符串
public static String getExpectStr(Integer old) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < STM[old].length; i++) {
Integer s = STM[old][i];
if (s != null) {
sb.append(Type.changeTypeToLocalStr(s)).append('|');
}
}
return sb.length()==0?null:sb.deleteCharAt(sb.length()-1).toString();//deleteCharAt(int a)只有一个参数,删除索引为a的字符
}
}
根据状态转化图可以得到10种转化状态,再通过状态转换图结合状态类型和数据类型就可以得到状态转换矩阵。 LeptType输入操作列表:当输入为这个类型的LeptType时,我要执行一个什么操作。目标状态转换操作列表:如果转换为这个状态的话,我会执行什么操作。
StatusMachine类:
package com.hl.myJson.Grammar;
import com.hl.myJson.Lexical.JsonLex;
import com.hl.myJson.Lexical.LeptType;
import com.hl.myJson.Lexical.Type;
import com.hl.myJson.Lexical.UnexpectedException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StatusMachine {
private JsonLex lex=null;
private Integer status=null;
Operator opt=null;
public StatusMachine(String str){
if (str == null)
throw new NullPointerException("语法解析构造函数不能传递null");
lex = new JsonLex(str);
opt = new Operator(lex);
}
public Object parse(){
LeptType lt=null;
status=Status.BGN;
Integer oldStatus=status;
while((lt=lex.leptParseValue())!=LeptType.EOF){
if(lt==null){
throw lex.generateUnexpectedException("发现JSON不能识别的LeptType: “"+lex.getCurChar()+"”");
}
if(status == Status.VAL || status == Status.EOF || status == Status.ERR){
throw lex.generateUnexpectedException("当前状态【"+Status.castLocalStr(oldStatus)+"】,期待【结束】;却返回"+lt.toLocalString());
}
oldStatus=status;
status=Status.STM[oldStatus][lt.getType()];
if(status==Status.ERR){
throw lex.generateUnexpectedException("当前状态【"+Status.castLocalStr(oldStatus)+"】,期待【"+(Status.ETS[oldStatus]==null?"结束":Status.ETS[oldStatus])+"】;却返回"+lt.toLocalString());
}
try {
Method m = Status.LtInput[lt.getType()];
if (m != null) {//输入Token操作
/**
* Object:invoke(Object obj,Method method,Object[] args)
* obj一般是指代理类
* method是被代理的方法
* args为该方法的参数数组
*/
status = (Integer) m.invoke(opt, oldStatus, status, lt);
}
m=Status.LtGoal[status];
if(m!=null){//目标状态操作
status = (Integer)m.invoke(opt, oldStatus, status, lt);
}
} catch (IllegalArgumentException e) {
throw lex.generateUnexpectedException("【反射调用】传入非法参数",e);
} catch (IllegalAccessException e) {
throw lex.generateUnexpectedException("【反射调用】私有方法无法调用",e);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof UnexpectedException) {
throw (UnexpectedException) e.getTargetException();
} else {
throw lex.generateUnexpectedException("运行时异常", e);
}
}
}
return opt.getCurValue();
}
}
这个类的核心甚至整个解析器的核心就是 parse()方法。核心内容就是一个while循环,从词法分析器获得LeptType作为输入看看是不是文件结束类型,是,就跳出循环,结束解析,返回操作对象中的当前值。每次循环,开始先做一些检查,报错的报错,根据状态转换矩阵进行状态转换,当前得到三个参数,一个旧状态,一个新状态,一个输入的Token,再做一次ERR检查,该报错的报错。然后看看当前类型包不包含数组和对象这两种复合类型,包含就给分配空间并且入栈,如果上一个状态不是开始状态,将状态入statusStack。最后在看看当前有没有值要入栈。
为了代码简洁,这里用了反射执行。
Object:invoke(Object obj,Method method,Object[] args);
*obj一般是指代理类
*method是被代理的方法
*args为该方法的参数数组
通过这个方法来调用Operator类中的相关方法,实现要实现的功能。
OPT类定义了Operator类中这些方法的静态映射,用来反射调用。
Operator类,就是被StatusMachine类调用,实现一些出栈入栈操作。
语法解析测试结果:
词法解析测试结果:
到这里解析器就差不多介绍完了,难一点的就是状态转换的实现,如果理解不了的话,可以自己写个测试用例在代码上走一遍就彻底理解啦~