自己写的java版的JSON解析器详解

来源:互联网 发布:mac ps教程 编辑:程序博客网 时间:2024/05/29 03:57

自己写的java版的json解析器详解

前言

上回书说道,我用flex&bison写了个json解析的原理性示例,结果我那坑爹同事连看都不看一眼,我感到很桑心……

为了让这个同事能服我,我一定要写个java版的json解析……到时候一定让这个同事给我发一个大写的“服”字给我……

那同事还说,能写java版的json解析就可以去阿里工作了……我至今都觉得这是讽刺阿里没人才的高端黑……

因为,你看完了这篇文章,估计你也就能写个java版的json解析了……到时候咱一起去把阿里的门槛踏破呵~

另外,好多人不注意看文档结构……我发博客,后面肯定是会附上完整下载链接的……不想看文可以直接拖到后面下下来玩~

这次的程序有点长,我可能就不会粘贴所有代码了,提前告诉大家可以先下载后看文~

总体介绍

首先,这仍然是一个以探究原理为主的实现,结构清晰是第一,然后兼顾效率,我并不是想写一个比已有解析器厉害的东西……

当然,我自认为暂时还是没能力写出比已有的json第三方解析包厉害的东西……

项目的名字已经很明确了,multTravJsonParse——基于多次遍历的JSON解析……

其次,说一下,整个JSON解析使用的是遍历分析(词法解析)、状态机(语法解析)和堆栈(状态控制和操作),并非采用递归方式来实现的多次遍历解析……在语法分析时,是采用堆栈构造的……这跟递归是等效的,但是却可以免受JVM调用堆栈溢出问题的困扰……

先说一说文件结构吧,huaying1988神马鬼?不要在意这些细节,那是我因为身份证过期至今没备案成功的域名……

G:.└─com    └─huaying1988        ├─multTravJsonParse        │  │  JSON.java        │  │        │  ├─lex        │  │      JsonLex.java        │  │      TOK.java        │  │      Token.java        │  │      UnexpectedException.java        │  │        │  └─syntax        │          Operator.java        │          OPT.java        │          STATE.java        │          StateMachine.java        │        └─test                TestJsonLex.java                TestJSONParse.java
结构很明显,分为四部分:

  1. JSON.java——这是整个解析器的入口类,里面就一个静态方法parse,内容也很简单,就是创建StateMachine对象并调用parse方法,这个类大家都能看懂,不会再下面再详细讲了……
  2. lex包——这个很明显,是词法解析的包,里面定义了一个异常类,一个包含Token类型常量的TOK类,一个用来存储解析结果单元的Token类,还有一个词法解析器JsonLex类
  3. syntax包——也很明显,这个是语法解析的包,里面包含了一个包含状态常量定义的STATE类,一个状态机StateMachine类,一个包含操作常量OPT类,以及一个用来操作的Operator类
  4. test包——很明显,用来测试的,包含一个测试语法解析的TestJsonLex类,一个测试JSON解析的TestJSONParse类,这个类为了测试对比,引入了第三方的JSON解析包,那就是阿里某大牛的fastJSON包~
好了,总体的结构就是这样子,下面来一个一个的分析~

词法解析器

lex包里一共就四个文件,自定义的这个异常类我就不说了,主要是用来报错的时候好定位的……因为lex是整个json解析中离代码最近的一个处理,所以,所有的异常最后还是要在词法分析处生成比较好,因为它比较容易记录当前的行号和列号以及总字符数,报错的时候也容易告诉用户这些信息,所以JsonLex中有generateUnexpectedException方法,行号和列开头的堆栈,还有在nextChar和revertChar里面有关于行号的列号相关的处理操作……于是关于这个异常类,我就不贴代码了,目的很明确,大家估计也看得懂……

首先,要从TOK这个类开始,这里面记载类总共有多少个Token类型,当然,这个Token类型时从上一篇博客中json.l中直接扒过来的……

TOK.java代码

package com.huaying1988.multTravJsonParse.lex;/** * 保存Token类型信息以及相关静态变量 *  * @author huaying1988.com *  */public class TOK {public static final int STR = 0;public static final int NUM = 1;public static final int DESC = 2;public static final int SPLIT = 3;public static final int ARRS = 4;public static final int OBJS = 5;public static final int ARRE = 6;public static final int OBJE = 7;public static final int FALSE = 8;public static final int TRUE = 9;public static final int NIL = 10;public static final int BGN = 11;public static final int EOF = 12;/** * 并非tok类型,存储tok类型的个数,添加类型时请同步修改 */public static final int TOK_NUM = 13;/** * 将tok类型转换为字符串的转换数组,添加类型时请同步修改 */public static final String[] CAST_STRS = { "STR", "NUM", "DESC", "SPLIT","ARRS", "OBJS", "ARRE", "OBJE", "FALSE", "TRUE", "NIL", "BGN", "EOF" };/** * 将tok类型转换为字符串的转换数组,添加类型时请同步修改 */public static final String[] CAST_LOCAL_STRS = { "字符串", "数字", ":", ",","[", "{", "]", "}", "false", "true", "null", "开始", "结束" };/** * 将tok类型转换为String,测试用显示结果的 *  * @return */public static String castTokType2Str(int type) {if (type < 0 || type > TOK_NUM)return "undefine";elsereturn CAST_STRS[type];}/** * 将tok类型转换为String,用于报错信息 *  * @return */public static String castTokType2LocalStr(int type) {if (type < 0 || type > TOK_NUM)return "undefine";elsereturn CAST_LOCAL_STRS[type];}}

里面保存了13个token类型,同时还包括对类型转换为字符串的方法……

之后便是重头戏,JsonLex.java,刚才说了它要记录行列字符进行字符串遍历(nextChar)、遍历控制(reverseChar),当然,词法解析才是这个类的重点。

package com.huaying1988.multTravJsonParse.lex;import java.util.Stack;/** * Json词法分析器 *  * @author huaying1988.com *  */public class JsonLex {// 当前行号private int lineNum = 0;// 用于记录每一行的起始位置private Stack<Integer> colMarks = new Stack<Integer>();// 用于报错的行游标private int startLine = 0;// 用于报错的列游标private int startCol = 0;// 当前字符游标private int cur = -1;// 保存当前要解析的字符串private String str = null;// 保存当前要解析的字符串的长度private int len = 0;/** * JsonLex构造函数 *  * @param str *            要解析的字符串 */public JsonLex(String str) {if (str == null)throw new NullPointerException("词法解析构造函数不能传递null");this.str = str;this.len = str.length();this.startLine = 0;this.startCol = 0;this.cur = -1;this.lineNum = 0;this.colMarks.push(0);}public char getCurChar(){if (cur >= len - 1) {return 0;}else{return str.charAt(cur);}}public Token parseSymbol(char c) {switch (c) {case '[':return Token.ARRS;case ']':return Token.ARRE;case '{':return Token.OBJS;case '}':return Token.OBJE;case ',':return Token.SPLIT;case ':':return Token.DESC;}return null;}public boolean isLetterUnderline(char c) {return ((c >= 'a' && c <= 'z') || (c >= 'a' && c <= 'z') || c == '_');}public boolean isNumLetterUnderline(char c) {return ((c >= 'a' && c <= 'z') || (c >= 'a' && c <= 'z')|| (c >= '0' && c <= '9') || c == '_');}public boolean isNum(char c) {return (c >= '0' && c <= '9');}public boolean isDecimal(char c) {return ((c >= '0' && c <= '9') || (c == '.'));}private void checkEnd() {if (cur >= len - 1) {throw generateUnexpectedException("未预期的结束,字符串未结束");}}public UnexpectedException generateUnexpectedException(String str) {return new UnexpectedException(cur,startLine, startCol, str);}public UnexpectedException generateUnexpectedException(String str,Throwable e) {return new UnexpectedException(cur,startLine, startCol, str, e);}private String getStrValue(char s) {int start = cur;char c;while ((c = nextChar()) != 0) {if (c == '\\') {// 跳过斜杠以及后面的字符c = nextChar();} else if (s == c) {return str.substring(start + 1, cur);}}checkEnd();return null;}private String getNumValue() {int start = cur;char c;while ((c = nextChar()) != 0) {if (!isDecimal(c)) {return str.substring(start, revertChar());}}checkEnd();return null;}private Token getDefToken() {int start = cur;char c;while ((c = nextChar()) != 0) {if (!isNumLetterUnderline(c)) {String value = str.substring(start, revertChar());if ("true".equals(value)) {return Token.TRUE;} else if ("false".equals(value)) {return Token.FALSE;} else if ("null".equals(value)) {return Token.NIL;} else {return new Token(TOK.STR, value);}}}checkEnd();return null;}/** * 获取下一个字节,同时进行 行、列 计数 *  * @return 下一个字节,结束时返回0 */private char nextChar() {if (cur >= len-1) {return 0;}++cur;char c = str.charAt(cur);if (c == '\n') {++lineNum;colMarks.push(cur);}return c;}/** * 撤回一个字节,同时进行 行、列 计数,返回撤回前的字符游标 *  * @return 下一个字节,结束时返回0 */private int revertChar() {if (cur <= 0) {return 0;}int rcur = cur--;char c = str.charAt(rcur);if (c == '\n') {--lineNum;colMarks.pop();}return rcur;}public static boolean isSpace(char c) {return (c == ' ' || c == '\t' || c == '\n');}// str \"(\\\"|[^\"])*\"// def [_a-zA-Z][_a-zA-Z0-9]*// num -?[0-9]+(\.[0-9]+)?// space [ \t\n]+/** * 获取下一个Token的主函数 */public Token next() {if (lineNum == 0) {lineNum = 1;return Token.BGN;}char c;while ((c = nextChar()) != 0) {startLine = lineNum;startCol = getColNum();if (c == '"' || c == '\'') {return new Token(TOK.STR, getStrValue(c));} else if (isLetterUnderline(c)) {return getDefToken();} else if (isNum(c) || c=='-') {return new Token(TOK.NUM, getNumValue());} else if (isSpace(c)) {continue;} else {return parseSymbol(c);}}if (c == 0) {return Token.EOF;}return null;}public int getLineNum() {return lineNum;}public int getColNum() {return cur - colMarks.peek();}public int getCur() {return cur;}public String getStr() {return str;}public int getLen() {return len;}}
构造函数初始化就不说了……一些简单的Get/Set、字符判断也不说了……词法解析的主函数入口是这个类中的next,负责返回下一个Token……

同事问我Token是啥……Token就是一个实体对象类,里面主要存了两个属性,类型和值……词法解析的主要作用是预处理,将复杂的字符串转换为相对简单而统一的类型,而这个统一类型,我们把它称之为Token,而词法解析就是讲字符流转换为Token流的一个过程……我想,这么说同事应该能听懂……

当然,大部分的Token只需要类型就行了,只有少数复杂的Token类型有值,那就是STR(字符串)、NUM(数字)这两种类型的Token……

Next这个方法循环调用nextChar获取下一个字符,碰见某种类型的初始字符,就开始进入相应Token类型的处理函数中,最终返回Token类型的对象……

其中parseSymbol,字符类型的Token没什么好说的……最麻烦的就是STR(字符串)、NUM(数字)这两种类型,它们除了有类型还有值,处理函数分别是getStrValue和getNumValue……行数都不多,大约看看也能懂……空格字符略过,没什么好说的……除此之外,还有一个getDefToken,这个一方面是用来处理true、false、null的,看过上篇文章中json.l文件的大约能懂这个是啥……另一方面是针对不严格JSON中的key的……因为我经常写不严格的JSON,所以,这个要能处理,可以看到,这种情况作为STR(字符串)类型处理了……

最后看看Token这个实体类:

package com.huaying1988.multTravJsonParse.lex;/** * 保存token *  * @author huaying1988.com *  */public class Token {public static final Token DESC = new Token(TOK.DESC);public static final Token SPLIT = new Token(TOK.SPLIT);public static final Token ARRS = new Token(TOK.ARRS);public static final Token OBJS = new Token(TOK.OBJS);public static final Token ARRE = new Token(TOK.ARRE);public static final Token OBJE = new Token(TOK.OBJE);public static final Token FALSE = new Token(TOK.FALSE);public static final Token TRUE = new Token(TOK.TRUE);public static final Token NIL = new Token(TOK.NIL);public static final Token BGN = new Token(TOK.BGN);public static final Token EOF = new Token(TOK.EOF);// 从TOK类中定义的类型private Integer type;// 该tok的值private String value;public Token(int type) {this.type = type;this.value = null;}public Token(int type, String value) {this.type = type;this.value = value;}public int getType() {return type;}public void setType(int type) {this.type = type;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public static String unescape(String str) {StringBuffer sb = new StringBuffer();for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);if (c == '\\') {c = str.charAt(++i);switch (c) {case '"':sb.append('"');break;case '\\':sb.append('\\');break;case '/':sb.append('/');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 = str.substring(i+1, i+5);sb.append((char)Integer.parseInt(hex, 16));i+=4;break;default:throw new RuntimeException("“\\”后面期待“\"\\/bfnrtu”中的字符,结果得到“"+c+"”");}}else{sb.append(c);}}return sb.toString();}public Object getRealValue(){Object curValue = null;switch(this.getType()){case TOK.TRUE:curValue = true;break;case TOK.FALSE:curValue = false;break;case TOK.NIL:curValue = null;break;case TOK.NUM:if(value.indexOf('.')>=0){curValue = Double.parseDouble(value);}else{curValue = Integer.parseInt(value);}break;case TOK.STR:curValue = unescape(value);break;}return curValue;}public String toString() {if (this.type > 1) {return "[" + TOK.castTokType2Str(this.type) + "]";} else {return "[" + TOK.castTokType2Str(this.type) + ":" + this.value+ "]";}}public String toLocalString() {if (this.type > 1) {return "“" + TOK.castTokType2LocalStr(this.type) + "”";} else {return "“" + TOK.castTokType2LocalStr(this.type) + ":" + this.value+ "”";}}}

这个实体类把所有没有值得Token都定义了静态常量,这个无非就是刚才说的兼顾效率,每次都new没什么意思,当然第二点是为了对比判断方便……

对于不含值的Token类型可以直接用==判断,因为两个指向的是同一个对象,所以能直接进行指针判断,也是挺爽的不是……尤其是对于EOF(文件结束)类型的Token,判断循环结束条件尤其好用……

除了这些静态常量,还有刚才说的类型、值两个属性,get/set方法,还有toString方法之外,不得不提的是getRealValue和unescape这两个方法……

有值的就那么几个……NUM类型还判断了一下是int类型还是double类型……这些都不难理解……unescape方法是将字符串中的转义字符化解掉……变成真正实际的字符串……

而这个unscape方法就是传说中的对字符串的第二次遍历……对应项目名多次遍历,这就是其中一个点……如果设计巧妙点可以一次性的解决STR(字符串)、NUM(数字)类型的值得问题,但是,就当前项目来说,写到这份上,也是可以了……也不枉担一个多次遍历的名分……

这样,整个词法分析器就讲完了……貌似没什么太难以理解的地方……跟上篇文章一样,词法解析一般都不成难点,等你从词法分析入坑之后才发现:麻烦才刚刚开始……

语法解析器

接下来的事情变得高级起来了……高级得有时候一下子就不懂了……更确切的说是——太抽象了……

编译原理难学就在于它太抽象了……用到了很多极端抽象的东西……比如说,下面要说的状态自动机就是这么个东西……

上篇文章已经说了,这个JSON解析器的总体实现都是一晚上搞出来的东西……这一晚上在语法解析器这儿,我曾经设想过好多的方案……

比如说采用yacc/bison那样语法树的输入形式,然后进行一个树状遍历……但是我终究没这么干……因为我毕竟不想写一个像yacc/bison那样的通用解析器……

于是我选择了状态自动机……说起状态自动机来,这是编译原理里面比较晦涩的一个东西……但是当我把JSON演变的状态自动机画出来,也不过如此了……

画的不好看,很乱,其中还可能有错误,但是不想改了,维持原样吧……这个图是第二天给那个同事讲解的时候画的,然而因为那天晚上熬到很晚,所以第二天画这个图的时候精神状态自然也不怎么样,大家凑合着看吧:


同事问,你在实现这个的时候,是先画了这么张图么?当然——没有……我要是画了这么张图,也不至于花了一晚上去实现啊……

这是先实现的,后有了这张图啊……看不懂?没关系,接着再往下看,慢慢就懂了……

先看一下状态定义类STATE.java:

package com.huaying1988.multTravJsonParse.syntax;import java.lang.reflect.Method;import com.huaying1988.multTravJsonParse.lex.TOK;/** * 保存状态列表、状态转换矩阵等静态常量 * @author huaying1988.com * */public class STATE {//开始态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 NIL 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,OBJAK,ERR,ERR,ERR,ERR,ERR,VAL,ERR,ERR,ERR,ERR},/*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*/{}//没有后续状态,占位,方便后期添加};//Token输入操作列表/*INPUT —— STR NUM DESC SPLIT ARRS OBJS ARRE OBJE FALSE TRUE NIL BGN*/public static final Method[] TKOL = {null,null,null,null,OPT.ARRS,OPT.OBJS,null,null,null,null,null,null};//目标状态转换操作列表/*TO:BGN ARRBV ARRAV OBJBK OBJAK OBJBV OBJAV VAL EOF ERR*/public static final Method[] STOL = {null,null,OPT.ARRAV,null,OPT.OBJAK,null,OPT.OBJAV,OPT.VAL,null,null};//期望Token描述列表/*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),TOK.castTokType2LocalStr(TOK.EOF),TOK.castTokType2LocalStr(TOK.EOF),TOK.castTokType2LocalStr(TOK.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){StringBuffer sb = new StringBuffer();for(int i=0;i<STM[old].length;i++){Integer s = STM[old][i];if(s != ERR){sb.append(TOK.castTokType2LocalStr(i)).append('|');}}return sb.length() == 0 ? null : sb.deleteCharAt(sb.length()-1).toString();}}
这个类里面分为这么几部分:

  1. 状态的定义,全是静态常量,凑了个整数,正好10个
  2. 一个状态转换矩阵的定义,行是状态,列是输入的Token,整个矩阵的意义是:该行的状态在遇见该列的Token类型时转换为什么状态
  3. 两个操作列表,一个是Token输入操作列表,意思是,当输入为这个类型的Token时,我要执行一个什么操作;还有一个是目标状态转换操作列表,意思是,如果转换为这个状态的话,我会执行什么操作。所映射的操作与OPT和Operator这两个类相关,后面再说……
  4. 一个期望Token描述列表以及对应的一个实现方法,这个列表里存的是,当前状态下输入哪些Token是正确的,其实也就是从状态转换矩阵里,把状态对应行中不是ERR(错误)的Token取出来连成字符串,这个字符串列表是为报错用的……如果出现错误,我们就可以提示用户:在哪一行哪一列,期望什么,但是却得到了什么……
  5. 将状态转换为字符串的列表及方法,对应数字就能找到相应的描述,再对应上面那个图,大体上就会有感觉了……

相信,有了这个描述,再加上这个状态转换矩阵对应上面状态转换图一起看,大家应该都会对状态机有一个整体的概念和把握了……

接下来就是状态机的实现了……看StateMachine类,反而很简单,就一个方法……比起词法解析来代码少多了……先看一下内容:

package com.huaying1988.multTravJsonParse.syntax;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import com.huaying1988.multTravJsonParse.lex.JsonLex;import com.huaying1988.multTravJsonParse.lex.Token;import com.huaying1988.multTravJsonParse.lex.UnexpectedException;/** * 语法状态机,负责状态转换,以及相关操作的调用 * @author huaying1988.com * */public class StateMachine {private JsonLex lex = null;private Operator opt = null;private Integer status = null;public StateMachine(String str){if (str == null)throw new NullPointerException("语法解析构造函数不能传递null");lex = new JsonLex(str);opt = new Operator(lex);}public Object parse(){Token tk = null;status = STATE.BGN;Integer oldStatus = status;while((tk=lex.next())!=Token.EOF){if(tk == null){throw lex.generateUnexpectedException("发现不能识别的token:“" + lex.getCurChar() + "”");}if(status == STATE.VAL || status == STATE.EOF || status == STATE.ERR){throw lex.generateUnexpectedException("当前状态【"+STATE.castLocalStr(oldStatus)+"】,期待【结束】;却返回"+tk.toLocalString());}oldStatus = status;status = STATE.STM[oldStatus][tk.getType()];if(status == STATE.ERR){throw lex.generateUnexpectedException("当前状态【"+STATE.castLocalStr(oldStatus)+"】,期待【"+(STATE.ETS[oldStatus]==null?"结束":STATE.ETS[oldStatus])+"】;却返回"+tk.toLocalString());}try {Method m = STATE.TKOL[tk.getType()];if(m!=null){//输入Token操作status = (Integer)m.invoke(opt, oldStatus, status, tk);}m = STATE.STOL[status];if(m!=null){//目标状态操作status = (Integer)m.invoke(opt, oldStatus, status, tk);}} 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方法了……JSON.java类就是调的这个方法……看来这就已经到了核心方法了!

然而这个核心方法也没几行啊……核心内容就是一个while循环,从词法分析器的上游获得Token作为输入……看看是不是文件结束类型的Token,是,就跳出循环,返回操作对象中的当前值……

每次循环,开始先做一些检查,该报错的报错,根据状态转换矩阵进行状态转换,当前得到三个参数,一个旧状态,一个输入的Token,一个新状态,再做一次ERR检查,该报错的报错……

然后,看看Token操作列表里有该Token类型相关的绑定操作没,有的话执行,没有的话跳过;再看看状态操作列表里有没有目标状态绑定的操作,有的话执行,传值都是这三个参数:一个旧状态,一个输入的Token,一个新状态。

这个地方用的反射执行,为啥没用接口类的多重实现的方式呢?因为那样写太不紧凑了……如果我每一个操作都写一个操作接口的操作类实现的话,我要写好多的类,这样一点都不漂亮,而且把代码搞得很分散,根本不容易看,更不好给大家讲解不是?所以,用反射的方式,一个类里可以包含所有你想要的方法,所有的实现都在一个类里,多漂亮……这就叫用一种形式上的完美来代替另一种形式上的完美……

上篇文章我说过了,语法分析是一道坎……这个核心弄懂了,大约整个解析器就弄得差不多了……不管你信不信,反正经过我的讲解之后,那个同事把状态机、状态转换矩阵突然茅塞顿开般的弄懂了,这是很大的进步……

然而,后面还有一道坎,你懂得~

配套堆栈处理

状态机有了,但是总要用它来干点事,那就是对应的操作了……

刚才已经看到了,所有的操作映射都挂在了STATE类里……

其实到这一块,就跟上一篇文章又很像了……

因为语法分析上,yacc/bison用了一种通用的抽象描述手段,语法树;而我用了一种通用的抽象解析手段,状态机……

其本质是一样的……最后回归到操作上,连形式也一样了……

先看一下操作类Operator.java:

package com.huaying1988.multTravJsonParse.syntax;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Stack;import com.huaying1988.multTravJsonParse.lex.JsonLex;import com.huaying1988.multTravJsonParse.lex.TOK;import com.huaying1988.multTravJsonParse.lex.Token;/** * 该类负责实际操作 *  * @author huaying1988.com *  */public class Operator {private JsonLex lex = null;private Stack<Integer> statusStack = new Stack<Integer>();private Object curValue = null;private Stack<Object> keyStack = new Stack<Object>();private Stack<Object> objStack = new Stack<Object>();private Object curObj = null;public Object getCurObj() {return curObj;}public Operator(JsonLex lex) {this.lex = lex;}public Object getCurValue(){return curValue;}public Integer objs(Integer from, Integer to, Token input) {if (from != STATE.BGN) {statusStack.push(from);}curObj = new HashMap<Object, Object>();objStack.push(curObj);return to;}public Integer arrs(Integer from, Integer to, Token input) {if (from != STATE.BGN) {statusStack.push(from);}curObj = new ArrayList<Object>();objStack.push(curObj);return to;}@SuppressWarnings("unchecked")public Integer val(Integer from, Integer to, Token input) {switch (input.getType()) {case TOK.ARRE:case TOK.OBJE:curObj = objStack.pop();curValue = curObj;break;case TOK.TRUE:case TOK.FALSE:case TOK.NIL:case TOK.NUM:case TOK.STR:curValue = getRealValue(input);break;}if (statusStack.isEmpty()) {return STATE.EOF;}else{Integer s = statusStack.pop();if (s == STATE.ARRBV) {curObj = objStack.peek();((List<Object>) curObj).add(curValue);s = STATE.ARRAV;} else if (s == STATE.OBJBV) {curObj = objStack.peek();((Map<Object, Object>) curObj).put(keyStack.pop(), curValue);s = STATE.OBJAV;}return s;}}private Object getRealValue(Token input) {Object value = null;try {value = input.getRealValue();} catch (RuntimeException e) {lex.generateUnexpectedException("字符串转换错误", e);}return value;}@SuppressWarnings("unchecked")public Integer arrav(Integer from, Integer to, Token input) {curValue = getRealValue(input);((List<Object>) curObj).add(curValue);return to;}public Integer objak(Integer from, Integer to, Token input) {keyStack.push(getRealValue(input));return to;}@SuppressWarnings("unchecked")public Integer objav(Integer from, Integer to, Token input) {curValue = getRealValue(input);((Map<Object, Object>) curObj).put(keyStack.pop(), curValue);return to;}}
看过上一篇文章的同学会觉得很眼熟吧,眼熟的东西有这么几个属性:curObj、curValue、objStack

不认识的有这么两个属性:stateStack、keyStack……

学习好的同学可能记起来了我上一篇文章中说的话:yacc/bison内置有两个栈,一个状态栈,一个值栈

而这两个Stack就是传说中的这两个栈……一一暴露在了我们的眼前……

我思考前后,觉得还是把这个栈放在这个类里比较好,因为想想yacc/bison里面这些东西都是紧密相关的……

因为这两个栈本来也不属于状态机的东西……

好了,除了这几个属性,还有get方法,剩下的,都是用于操作的方法了……

什么出栈入栈啦……来回赋值啦……put、add啦……跟上篇文章写的js生成如出一辙,异曲同工……

唯一与状态机沾点边的就是val这个操作,这个操作会在状态栈里拿出一个状态来进行运算后,返回一个新的状态作为状态机的新状态……

这是很重要的一步,也就是在遍历表达式树状结构的时候必要的一步状态回溯操作……学过数据结构的好好想想就能想明白……

至于其他的,没什么好说的……最后再看看OPT.java这个类:

package com.huaying1988.multTravJsonParse.syntax;import java.lang.reflect.Method;import com.huaying1988.multTravJsonParse.lex.Token;/** * 保存操作相关的静态常量 * @author huaying1988.com * */public class OPT {public static final Method VAL = getMethod("val");public static final Method ARRAV = getMethod("arrav");public static final Method OBJAK = getMethod("objak");public static final Method OBJAV = getMethod("objav");public static final Method ARRS = getMethod("arrs");public static final Method OBJS = getMethod("objs");public static Method getMethod(String methodName){Method m =  null;try {m = Operator.class.getMethod(methodName, new Class[]{Integer.class,Integer.class,Token.class});} catch (SecurityException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}return m;}}
这个类里就定义了Operator类中这些方法的静态映射……用来反射调用的时候用的……其中为了简便起见,还写了一个配套的方法返回相应的Method对象……


好了,到此为止,整个JSON解析器就讲完了……这个JSON解析器返回的是List、Map的嵌套结构对象……其实本来还想继续实现一个返回JavaBean对象得JSON解析方法……

这要通过反射注入的方式来设置对象的属性值,比生成List、Map嵌套结构要略微复杂一些难度的……但是想来,原理已经弄清楚了,具体实现到哪一步也完全看心情了吧……

是时候来个尾声了呢……

下载链接

按照惯例,最后还是要给个下载链接的~

后话

那个求问的同事给了我一个大大的服……然而,我到现在也没去成阿里啊……哈哈哈哈……



0 0