在开始正文之前,先给大家推荐一些学习技巧:
- 大牛的C语言版JOSN,万事开头难,我开始在网上找资料就花了很长时间,而且资料太多,众说纷纭…但是最后找到了这个大牛的,真的写的超级好,超级详细,虽然他是用C语言写的,哈哈~就这样我跟着大牛的文档和代码写了一遍C语言的JSON后,对JSON的语法以及实现有了一个初级的了解,然后,我就开始着手写Java版的JSON啦
- 在线JOSN校验格式化工具,如果在解析字符串的时候,拿不准这个是不是正确的JOSN,你可以在这个上面测试一下,有利于对自己代码的测试(我的测试代码写的特别挫,后序再改进,哈哈)
- 温馨提示:代码有点长,所以不会全部粘贴,可以先下载(Java实现Json解析器)下来再看文哦~
- 温馨提示:不建议大家看源码…因为我在写之前,为了看起来专业一点,然后我就想要不要用一下人家的源码的接口或者参考一下人家的代码什么的…然后我就开始了看fastjosn源码的不归路…看了一下午,我放弃了,实在是看不太懂哇~但是要是想看的话也可以看看,学学人家的代码,也没有什么坏处
总体介绍
其次,说一下,整个JSON解析使用的是遍历分析(词法解析)、灵活应用堆栈来实现(语法解析),没有使用递归方式,这样就免受了JVM调用堆栈溢出问题的困饶。
先看看文件结构:
Lexical-----词法解析,就是字符串解析(主要就是解析为true、false、null、String、num这几种基本类型)。Type类,定义了JOSN的数据类型;JsonLex类就是粗略的判断了下输入数据的JSON类型和大概的值;LeptType类保存数据的类型和真实值;UnexpectedException类自定义一个捕获错误类,用于报错。
Grammar-----语法解析,就是利用栈和HashMap等数据结构来解析数组和对象这两种符合结构。STATE类,定义状态常量;StateMachine类(总指挥官)将所有状态转化都联系起来实现JSON解析器;OPT类定义操作常量;Operator类具体操作类。
看文档顺序
- 词法解析:TestJsonLex->Type->JsonLex->LeptType->UnexpectedException
- 语法解析:TestJsonGrammar->JSON->Status->StatusMachine->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<value.length();i++){
char ch=value.charAt(i);
if(ch=='\\') {
if(i<value.length()-1)i++;
ch=value.charAt(i);
switch (ch) {
case '"':sb.append(ch);break;
case '\\':sb.append(ch);break;
case '/':sb.append(ch);break;
case 'b':sb.append('\b');break;
case 'f':sb.append('\f');break;
case 'n':sb.append('\n');break;
case 'r':sb.append('\r');break;
case 't':sb.append('\t');break;
case 'u':
String hex = value.substring(i+1, i+5);
//----------将16进制数转化成十进制,然后又强转成char类型-----我感觉有问题,char一个字节,还有码点的转换等
//----------而且也没有判断这个16进制合不合法------高低代理没有考虑
sb.append((char)Integer.parseInt(hex, 16));
i+=4;
break;
default:
throw new RuntimeException("“\\”后面期待“\"\\/bfnrtu”中的字符,结果得到“"+ch+"”");
}
}else {
sb.append(ch);
}
}
return sb.toString();
}
//因为所有类型Object都包含
public Object getRealValue(){
Object realValue=null;
switch (this.getType()){
case Type.TRUE:realValue=true;break;
case Type.FALSE:realValue=false;break;
case Type.NULL:realValue=null;break;
case Type.NUM:
realValue = Double.parseDouble(value);
break;
case Type.STR:
realValue = unescape(value);
break;
}
return realValue;
}
public String toString() {
if (this.type > 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类调用,实现一些出栈入栈操作。
语法解析测试结果:
词法解析测试结果:
到这里解析器就差不多介绍完了,难一点的就是状态转换的实现,如果理解不了的话,可以自己写个测试用例在代码上走一遍就彻底理解啦~