Java实现自己的Json解析器——Json字符串解析原理
来源:互联网 发布:阿里云大学官网 编辑:程序博客网 时间:2024/05/22 10:46
概述
附上完整的代码:
https://pan.baidu.com/s/1dEDmGz3
(入口类是Json)
JSON:JavaScript 对象表示法(JavaScript Object Notation)。
JSON 是存储和交换文本信息的语法。类似 XML。
JSON 比 XML 更小、更快,更易解析。
在JSON中,分为6种对象:
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组(JsonArray)
- 对象(JsonObject)
- null
基本对象的实现
JsonObject其实就是一个HashMap,JsonArray其实就是一个ArrayList.
public class JsonObject extends HashMap<String,Object> {}
public class JsonArray extends ArrayList<Object> {}
JSON字符串的解析
以这个字符串为例:
{“success”:true,”id”:-10.5,”employees”:[{“firstName”:”Bill”,”lastName”:”Gates”},{“firstName”:”George”,”lastName”:”Bush”},{“firstName”:”Thomas”,”lastName”:”Carter”}]}
我们保证在只扫描一次整个的情况下,就将json结构解析成功。
传统的解析策略通常是通过词法分析,将json分为一个个的token,而这些token有着自己的类型和值;再通过语法分析构建一棵抽象语法树,进一步处理。通常需要定义一个enum枚举类型表示Token,或者用一个数组来表示Token。
enum表示
public enum TokenType { START_OBJ, END_OBJ, START_ARRAY, END_ARRAY, NULL, NUMBER, STRING, BOOLEAN, COLON, COMMA, END_DOC}
数组表示
public static final String[] CAST_STRS = { "STR", "NUM", "DESC", "SPLIT", "ARRS", "OBJS", "ARRE", "OBJE", "FALSE", "TRUE", "NIL", "BGN", "EOF" };
其实根本不需要这么复杂。依我看来,json的token只有五种:true/false/null(归为一种,因为它们是固定值)、number、string、object、array。也不用特别在意start和end的Token区分,比如 { 符号和 } 符号。从一个 { 符号开始,到下一个它对应的 } 符号都是属于同一个json object的。这里的 { 与 } 、[ 与 ] 符号都是一一对应的。因此我设计的结构是将所有json值都看作一个对象Object,我设计了一个nextObject()方法,它可以解析出json字符串中的下一个对象,然后在适当的时候装配即可。
public Object nextObject(){}
nextObject方法的实现
提取字符
public static boolean isSpace(char c){ return c == ' ' || c == '\r' || c == '\n'; } //方法得到当前字符,忽略空格、换行符 private char getChar(){ char c = json.charAt(pos); while(isSpace(c)){ pos++; c = getCurrentChar(); } return c; }
上面方法是消耗掉所有空白字符,直到读取到一个非空白字符,isSpace方法用于判断一个字符是否属于空白字符,pos表示当前指针指向的那个字符。也就是说,DFA从起始状态开始,若读到一个空字符,会在起始状态不断循环,直到遇到非空字符,状态转移情况如下:
解析
根据提取到的字符,转入不同的解析方法中,
例如字符是t,说明值可能是true,只需检查后面三个字符,如果是r、u、e,则可以直接返回true。
字符是f,说明值可能是false,只需检查后面四个字符,如果是a、l、s、e,则可以直接返回false。
碰到 \”,说明是字符串,在下一个\”出现之前,把扫描出来的字符都当成字符串中的字符,放到一个StringBuilder中去。
碰到 [ 符号,说明是数组了,就需要new一个JsonArray,在下一个 ] 符号出现之前,调用nextObject方法,把解析到的对象都放到这个JsonArray里面去。
碰到 { 符号,说明是JsonObject,就new一个JsonObject,这里每次需要连续调用两次nextObject,第一次结果作为key,第二次结果作为value。放到JsonObject中去。
解析boolean、null值
这类值的字符串只有固定的三种true、false、null,是最好解析的。在扫描到第一个字符为t、f、n时,只需检测后续字符是否符合固定值就可以了。checkChars方法实现了这个功能,chars是固定的序列,如果检测通过则返回true,否则返回false。
private boolean checkChars(char ...chars){ for(char ch : chars){ char c = getCurrentCharNext(); //得到当前字符,包括空格、换行符。将指针指向下一个字符 if(Character.toLowerCase(ch) != Character.toLowerCase(c)){ return false; } } return true; }
解析数字
解析数字的实现是parseNumber
方法,我们先new一个StringBuilder,向后扫描只要碰到0-9或者+-小数点,就添加到这个StringBuilder当中去,否则就StringBuilder.toString,将这个字符串转换成数字。
private Object parseNumber(char c){ StringBuilder numberBuilder = new StringBuilder(); boolean containsPoint = false; //字符串中是否包含小数点,如果不包含小数点则认为是int,否则认为是double. while (c >= '0' && c <= '9' || c == '+' || c == '-' || c == '.'){ if(c == '.'){ containsPoint = true; } numberBuilder.append(c); pos++; c = getCurrentChar(); //得到当前字符,包括空格、换行符 } if(numberBuilder.length() > 0){ String numberString = numberBuilder.toString(); try{ if(containsPoint){ return Double.valueOf(numberString); }else{ return Integer.valueOf(numberString); } }catch (NumberFormatException e){ throw new JsonException("invalid number value:" + numberString); } } return null; }
解析字符串
在json中字符串都是以双引号”开头,再以双引号”结尾的。当扫描到双引号”时,new一个StringBuilder,然后在下一个双引号”出现之前的每一个字符都需要添加到这个StringBuilder中去。需要注意的一点,字符串中是可能出现转义字符的。因此在扫描到一个字符为斜杠\时,需要取出下一个字符进行特殊处理。具体代码如下:
private String parseString(char c){ //字符串 pos++; StringBuilder sb = new StringBuilder(); while (true){ c = getCurrentCharNext(); if(c == '\\'){ //处理转义字符 char next = getCurrentChar(); //得到当前字符,包括空格、换行符 switch (next){ case 'r':sb.append('\r');break; case 'n':sb.append('\n');break; case 'b':sb.append('\b');break; case 't':sb.append('\t');break; case '\"':sb.append('\"');break; // case 'u': 处理unicode字符串,这里大家可以自行扩展 default: throw new JsonException("unknown escape char : " + next); } pos++; }else if(c == '\"'){ break; }else{ sb.append(c); } } return sb.toString(); }
解析JsonObject
连续调用两次nextObject,第一次结果作为key,第二次结果作为value。放到JsonObject中去。
注意逗号和冒号的处理。
private JsonObject parseObject(){ JsonObject jsonObject = new JsonObject(); boolean flag = false; //是否需要用逗号分割 char c = getCharNext(); //得到当前字符,忽略空格、换行符。将pos指针指向下一个字符 while (c != '}'){ if(flag){ if (c != ',') { throw new JsonException("illegal json object."); } pos++; } Object key = nextObject(); if(key == null || !(key instanceof String)){ throw new JsonException("illegal json object.key must be a string:" + key); } c = getCharNext(); if(c != ':'){ throw new JsonException("illegal json object."); } Object value = nextObject(); jsonObject.put((String) key,value); flag = true; c = getChar(); } pos++; return jsonObject; }
JsonArray的解析
在下一个 ] 符号出现之前,递归调用nextObject方法,把解析到的对象都放到这个JsonArray里面去。
返回
由于nextObject只返回一个对象,我们用nextObject方法处理整个json字符串。那么nextObject方法就会得到你需要的JsonObject。
扩展
参考 http://blog.csdn.net/dxiaolai/article/details/76359332
在大数据量的json场景下,不必将整个json字符串全部解析成json object后再处理,而是通过迭代器模式我们可以在解析字符串的同时使用对象。这样可以大大的提高程序的执行效率。
扩展ObjectParser类,使其成为一个迭代器,
public class ObjectParser implements Iterator<Object>{ public Object next(){ return nextObject(); } public boolean hasNext(){ return pos < json.length(); } @Override public void remove() { }}
这样就可以边解析边使用对象了。
ObjectParser parser = new ObjectParser ("json");while(parser.hasNext()){ Object object = parser.next();}
另外,对于超大数据量的json,通常由输入流的方式(InputStream
)提供,我们不必要一次性将流字节全部读入内存,而是可以逐字符的解析。实现方式是使用BuffererReader
,修改getChar等方法,每次读字符时从BuffererReader
中读取。
- Java实现自己的Json解析器——Json字符串解析原理
- java 解析 json 字符串
- java解析json字符串
- java解析json字符串
- java解析json字符串
- java解析json字符串
- java解析json字符串
- java json字符串解析
- java 解析json字符串
- java 解析json字符串
- java 解析json字符串
- java解析json字符串
- Java解析Json字符串
- java解析json字符串
- java 解析json字符串
- java解析json字符串
- java 解析json字符串
- java解析json字符串
- SpringBoot统一异常处理
- 谷歌开发者使用工具说明
- Computer science: The learning machines
- JavaScript正则表达式详解
- prettydate.js 插件
- Java实现自己的Json解析器——Json字符串解析原理
- C++实现一个string类
- 从搭建一个React项目,同时使用git把项目放到GitHub上
- SynchronizedList的同步问题
- PHP laravel系列之Blade模版
- jquery.treeview.js 插件
- spring boot跳过maven test
- DWR第三篇之逆向Ajax升级
- 用keras实现3层BP网络的训练、保存、加载和导入自己手写的数字进行测试