概述
附上完整的代碼:
https://files.cnblogs.com/files/xcr1234/json.rar
一個類實現json解析核心代碼(ObjectParser),其他的類都是工具類
(入口類是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有着自己的類型和值;再通過語法分析構建一棵抽象語法樹,進一步處理。比如""是一種,true/false又是一種。
其實根本不需要這么復雜。依我看來,json的token只有五種:true/false/null(歸為一種,因為它們是固定值)、number、string、object、array。也不用特別在意start和end的Token區分,比如 { 符號和 } 符號。從一個 { 符號開始,到下一個它對應的 } 符號都是屬於同一個json object的。這里的 { 與 } 、[ 與 ] 符號都是一一對應的。
我設計了一個nextObject()方法,它可以解析出json字符串中的下一個對象,然后在適當的時候裝配即可。
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; }
如果是true,就是`checkChars('t','r','u','e')`
解析數字
解析數字的實現是parseNumber
方法,我們先new一個StringBuilder,向后掃描只要碰到0-9或者+-小數點,就添加到這個StringBuilder當中去,否則就StringBuilder.toString,將這個字符串轉換成數字。
如果包含小數點,就用double,否則就用integer。
解析字符串
在json中字符串都是以雙引號”開頭,再以雙引號”結尾的。當掃描到雙引號”時,new一個StringBuilder,然后在下一個雙引號”出現之前的每一個字符都需要添加到這個StringBuilder中去。需要注意的一點,字符串中是可能出現轉義字符的。因此在掃描到一個字符為斜杠\時,需要取出下一個字符進行特殊處理。
解析JsonObject
連續調用兩次nextObject,第一次結果作為key,第二次結果作為value。放到JsonObject中去。
注意逗號和冒號的處理。
JsonArray的解析
在下一個 ] 符號出現之前,遞歸調用nextObject方法,把解析到的對象都放到這個JsonArray里面去。
返回
由於nextObject只返回一個對象,我們用nextObject方法處理整個json字符串。那么nextObject方法就會得到你需要的JsonObject。
超大json對象的解析
參考 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串,通常是以流的方式提供,我們不必要一次性將流字節全部讀入內存,而是可以逐字符的解析。每次讀取若干個字符,解析成對象;實現方式是使用BuffererReader
,修改getChar等方法,每次讀字符時從BuffererReader
中讀取。配合上面的迭代器模式,可考慮將一個BuffererReader封裝成Iterator<Object>。