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

自己写的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";
		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),当然,词法解析才是这个类的重点。

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嵌套结构要略微复杂一些难度的……但是想来,原理已经弄清楚了,具体实现到哪一步也完全看心情了吧……

是时候来个尾声了呢……

下载链接

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

后话

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



  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
JsonNode 是 Jackson 库中的一个类,用于表示 JSON 数据的节点。它可以表示 JSON 对象、数组、字符串、数字、布尔值和 null 值等。JsonNode 是一个抽象类,具体的实现有 ObjectNode、ArrayNode、TextNode、NumericNode、BooleanNode 和 NullNode 等。 JsonNode 提供了一系列的方法来访问和操作 JSON 数据,比如: 1. `get(String fieldName)`:获取指定字段名的节点。 2. `get(int index)`:获取指定索引位置的节点。 3. `getNodeType()`:获取节点的类型,返回一个 JsonNodeType 枚举类型。 4. `isArray()`、`isObject()`、`isTextual()`、`isNumber()`、`isBoolean()`、`isNull()`:判断节点的类型。 5. `asText()`、`asInt()`、`asDouble()`、`asBoolean()`:转换节点的值为对应的 Java 类型。 6. `elements()`:获取节点的所有子节点的迭代器。 7. `fields()`:获取节点的所有字段名和节点值的迭代器。 8. `path(String fieldName)`:获取指定字段名的节点,如果不存在则返回一个空节点。 9. `findPath(String fieldName)`:查找指定字段名的节点,如果不存在则返回一个空节点。 10. `findValue(String fieldName)`:查找指定字段名的节点,并返回它的节点值。 11. `findValues(String fieldName)`:查找指定字段名的所有节点,并返回它们的节点值。 JsonNode 的使用非常灵活,可以根据具体的需求来选择使用哪些方法。通常情况下,我们会使用 ObjectMapper 将 JSON 字符串转换为 JsonNode 对象,然后再进行访问和操作。例如: ```java ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(jsonStr); JsonNode nameNode = root.get("name"); String name = nameNode.asText(); ``` 以上代码将 JSON 字符串解析JsonNode 对象,并获取其中名为 "name" 的字段的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值