#自己写的java版的json解析器详解 **目录** [TOC] ##前言 [上回书](../flex_bison_json_resolver_explore/detail.html "上回书")说道,我用`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 ``` 结构很明显,分为四部分: - `JSON.java`——这是整个解析器的入口类,里面就一个静态方法`parse`,内容也很简单,就是创建`StateMachine`对象并调用`parse方法`,这个类大家都能看懂,不会再下面再详细讲了…… - `lex包`——这个很明显,是词法解析的包,里面定义了一个异常类,一个包含`Token类型常量`的`TOK类`,一个用来存储解析结果单元的`Token类`,还有一个词法解析器`JsonLex类` - `syntax包`——也很明显,这个是语法解析的包,里面包含了一个包含`状态常量`定义的`STATE类`,一个状态机`StateMachine类`,一个包含操作常量的`OPT类`,以及一个用来操作的`Operator类` - ·test包·——很明显,用来测试的,包含一个测试语法解析的`TestJsonLex类`,一个测试`JSON`解析的`TestJSONParse类`,这个类为了测试对比,引入了第三方的`JSON解析包`,那就是阿里某大牛的`fastJSON包`~ 好了,总体的结构就是这样子,下面来一个一个的分析~ ##词法解析器 `lex包`里一共就四个文件,自定义的这个异常类我就不说了,主要是用来报错的时候好定位的……因为`lex`是整个`json解析`中离代码最近的一个处理,所以,所有的异常最后还是要在词法分析处生成比较好,因为它比较容易记录当前的行号和列号以及总字符数,报错的时候也容易告诉用户这些信息,所以`JsonLex`中有`generateUnexpectedException`方法,行号和列开头的堆栈,还有在`nextChar`和`revertChar`里面有关于行号的列号相关的处理操作……于是关于这个异常类,我就不贴代码了,目的很明确,大家估计也看得懂…… 首先,要从`TOK`这个类开始,这里面记载类总共有多少个`Token`类型,当然,这个`Token`类型时从[上一篇博客](../flex_bison_json_resolver_explore/detail.html "上一篇博客")中`json.l`中直接扒过来的…… `TOK.java`代码: ```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"; else return CAST_STRS[type]; } /** * 将tok类型转换为String,用于报错信息 * * @return */ public static String castTokType2LocalStr(int type) { if (type < 0 || type > TOK_NUM) return "undefine"; else return CAST_LOCAL_STRS[type]; } } ``` 里面保存了13个`token`类型,同时还包括对类型转换为字符串的方法…… 之后便是重头戏,`JsonLex.java`,刚才说了它要记录行列字符进行字符串遍历(`nextChar`)、遍历控制(`reverseChar`),当然,词法解析才是这个类的重点。 ```java 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`这个实体类: ```java 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`演变的状态自动机画出来,也不过如此了…… 画的不好看,很乱,其中还可能有错误,但是不想改了,维持原样吧……这个图是第二天给那个同事讲解的时候画的,然而因为那天晚上熬到很晚,所以第二天画这个图的时候精神状态自然也不怎么样,大家凑合着看吧: ![json状态机](jsonStateMachine.png "json状态机") 同事问,你在实现这个的时候,是先画了这么张图么?当然——没有……我要是画了这么张图,也不至于花了一晚上去实现啊…… 这是先实现的,后有了这张图啊……看不懂?没关系,接着再往下看,慢慢就懂了…… 先看一下状态定义类`STATE.java`: ```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(); } } ``` 这个类里面分为这么几部分: - 状态的定义,全是静态常量,凑了个整数,正好10个 - 一个状态转换矩阵的定义,行是状态,列是输入的Token,整个矩阵的意义是:该行的状态在遇见该列的Token类型时转换为什么状态 - 两个操作列表,一个是Token输入操作列表,意思是,当输入为这个类型的Token时,我要执行一个什么操作;还有一个是目标状态转换操作列表,意思是,如果转换为这个状态的话,我会执行什么操作。所映射的操作与OPT和Operator这两个类相关,后面再说…… - 一个期望Token描述列表以及对应的一个实现方法,这个列表里存的是,当前状态下输入哪些Token是正确的,其实也就是从状态转换矩阵里,把状态对应行中不是ERR(错误)的Token取出来连成字符串,这个字符串列表是为报错用的……如果出现错误,我们就可以提示用户:在哪一行哪一列,期望什么,但是却得到了什么…… 将状态转换为字符串的列表及方法,对应数字就能找到相应的描述,再对应上面那个图,大体上就会有感觉了…… 相信,有了这个描述,再加上这个状态转换矩阵对应上面状态转换图一起看,大家应该都会对状态机有一个整体的概念和把握了…… 接下来就是状态机的实现了……看`StateMachine类`,反而很简单,就一个方法……比起词法解析来代码少多了……先看一下内容: ```java 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`,一个新状态。 这个地方用的反射执行,为啥没用接口类的多重实现的方式呢?因为那样写太不紧凑了……如果我每一个操作都写一个操作接口的操作类实现的话,我要写好多的类,这样一点都不漂亮,而且把代码搞得很分散,根本不容易看,更不好给大家讲解不是?所以,用反射的方式,一个类里可以包含所有你想要的方法,所有的实现都在一个类里,多漂亮……这就叫用一种形式上的完美来代替另一种形式上的完美…… [上篇文章](../flex_bison_json_resolver_explore/detail.html "上篇文章")我说过了,语法分析是一道坎……这个核心弄懂了,大约整个解析器就弄得差不多了……不管你信不信,反正经过我的讲解之后,那个同事把状态机、状态转换矩阵突然茅塞顿开般的弄懂了,这是很大的进步…… 然而,后面还有一道坎,你懂得~ ##配套堆栈处理 状态机有了,但是总要用它来干点事,那就是对应的操作了…… 刚才已经看到了,所有的操作映射都挂在了`STATE类`里…… 其实到这一块,就跟上一篇文章又很像了…… 因为语法分析上,`yacc`/`bison`用了一种通用的抽象描述手段,语法树;而我用了一种通用的抽象解析手段,状态机…… 其本质是一样的……最后回归到操作上,连形式也一样了…… 先看一下操作类`Operator.java`: ```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`…… 学习好的同学可能记起来了我[上一篇文章](../flex_bison_json_resolver_explore/detail.html "上一篇文章")中说的话:`yacc`/`bison`内置有两个栈,一个状态栈,一个值栈…… 而这两个`Stack`就是传说中的这两个栈……一一暴露在了我们的眼前…… 我思考前后,觉得还是把这个栈放在这个类里比较好,因为想想`yacc`/`bison`里面这些东西都是紧密相关的…… 因为这两个栈本来也不属于状态机的东西…… 好了,除了这几个属性,还有`get方法`,剩下的,都是用于操作的方法了…… 什么出栈入栈啦……来回赋值啦……`put`、`add`啦……跟上篇文章写的`js生成`如出一辙,异曲同工…… 唯一与状态机沾点边的就是`val`这个操作,这个操作会在状态栈里拿出一个状态来进行运算后,返回一个新的状态作为状态机的新状态…… 这是很重要的一步,也就是在遍历表达式树状结构的时候必要的一步状态回溯操作……学过数据结构的好好想想就能想明白…… 至于其他的,没什么好说的……最后再看看`OPT.java`这个类: ```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`嵌套结构要略微复杂一些难度的……但是想来,原理已经弄清楚了,具体实现到哪一步也完全看心情了吧…… 是时候来个尾声了呢…… ##下载链接 按照惯例,最后还是要[给个下载链接](http://pan.baidu.com/s/1boWSMgn "源码及工程下载链接")的~ ##后话 那个求问的同事给了我一个大大的**服**……然而,我到现在也没去成阿里啊……哈哈哈哈……


发表评论

必填,公开,这样称呼起来方便~

必填,不会被公开,不必担心~

http://

非必填,公开,目的是方便增进友好访问~

必填,请输入下方图片中的字母或数字,以证明你是人类

看不清楚
必填,最好不要超过500个字符
     ↑返回顶端↑