摘要:姓名張三年齡第二種數(shù)組值的有序列表。姓名張三年齡姓名里斯年齡通過(guò)上面的了解可以看出,存在以下幾種數(shù)據(jù)類型以做類比中的中的或中的中的中的或中的解析解析器的基本原理輸入一串字符串,輸出一個(gè)對(duì)象。
原文地址
JSONJSON(JavaScript Object Notation, JS 對(duì)象簡(jiǎn)譜) 是一種輕量級(jí)的數(shù)據(jù)交換格式。易于人閱讀和編寫。同時(shí)也易于機(jī)器解析和生成。采用完全獨(dú)立于語(yǔ)言的文本格式,但是也使用了類似于C語(yǔ)言家族的習(xí)慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成為理想的數(shù)據(jù)交換語(yǔ)言。
JSON與JS的區(qū)別以及和XML的區(qū)別具體請(qǐng)參考百度百科
JSON有兩種結(jié)構(gòu):
第一種:對(duì)象
“名稱/值”對(duì)的集合不同的語(yǔ)言中,它被理解為對(duì)象(object),紀(jì)錄(record),結(jié)構(gòu)(struct),字典(dictionary),哈希表(hash table),有鍵列表(keyed list),或者關(guān)聯(lián)數(shù)組 (associative array)。對(duì)象是一個(gè)無(wú)序的“‘名稱/值’對(duì)”集合。一個(gè)對(duì)象以“{”(左括號(hào))開(kāi)始,“}”(右括號(hào))結(jié)束。每個(gè)“名稱”后跟一個(gè)“:”(冒號(hào));“‘名稱/值’ 對(duì)”之間使用“,”(逗號(hào))分隔。
{"姓名": "張三", "年齡": "18"}
第二種:數(shù)組
值的有序列表(An ordered list of values)。在大部分語(yǔ)言中,它被理解為數(shù)組(array)。數(shù)組是值(value)的有序集合。一個(gè)數(shù)組以“[”(左中括號(hào))開(kāi)始,“]”(右中括號(hào))結(jié)束。值之間使用“,”(逗號(hào))分隔。
值(value)可以是雙引號(hào)括起來(lái)的字符串(string)、數(shù)值(number)、true、false、 null、對(duì)象(object)或者數(shù)組(array)。這些結(jié)構(gòu)可以嵌套。
[ { "姓名": "張三", "年齡":"18" }, { "姓名": "里斯", "年齡":"19" } ]
通過(guò)上面的了解可以看出,JSON存在以下幾種數(shù)據(jù)類型(以Java做類比):
json | java |
---|---|
string | Java中的String |
number | Java中的Long或Double |
true/false | Java中的Boolean |
null | Java中的null |
[array] | Java中的List |
{"key":"value"} | Java中的Map |
輸入一串JSON字符串,輸出一個(gè)JSON對(duì)象。
步驟JSON解析的過(guò)程主要分以下兩步:
第一步:對(duì)于輸入的一串JSON字符串我們需要將其解析成一組token流。
例如 JSON字符串{"姓名": "張三", "年齡": "18"} 我們需要將它解析成
{、 姓名、 :、 張三、 ,、 年齡、 :、 18、 }
這樣一組token流
第二步:根據(jù)得到的token流將其解析成對(duì)應(yīng)的JSON對(duì)象(JSONObject)或者JSON數(shù)組(JSONArray)
下面我們來(lái)詳細(xì)分析下這兩個(gè)步驟:
獲取token流根據(jù)JSON格式的定義,token可以分為以下幾種類型
token | 含義 |
---|---|
NULL | null |
NUMBER | 數(shù)字 |
STRING | 字符串 |
BOOLEAN | true/false |
SEP_COLON | : |
SEP_COMMA | , |
BEGIN_OBJECT | { |
END_OBJECT | } |
BEGIN_ARRAY | [ |
END_ARRAY | ] |
END_DOCUMENT | 表示JSON數(shù)據(jù)結(jié)束 |
根據(jù)以上的JSON類型,我們可以將其封裝成enum類型的TokenType
package com.json.demo.tokenizer; /** BEGIN_OBJECT({) END_OBJECT(}) BEGIN_ARRAY([) END_ARRAY(]) NULL(null) NUMBER(數(shù)字) STRING(字符串) BOOLEAN(true/false) SEP_COLON(:) SEP_COMMA(,) END_DOCUMENT(表示JSON文檔結(jié)束) */ public enum TokenType { BEGIN_OBJECT(1), END_OBJECT(2), BEGIN_ARRAY(4), END_ARRAY(8), NULL(16), NUMBER(32), STRING(64), BOOLEAN(128), SEP_COLON(256), SEP_COMMA(512), END_DOCUMENT(1024); private int code; // 每個(gè)類型的編號(hào) TokenType(int code) { this.code = code; } public int getTokenCode() { return code; } }
在TokenType中我們?yōu)槊恳环N類型都賦一個(gè)數(shù)字,目的是在Parser做一些優(yōu)化操作(通過(guò)位運(yùn)算來(lái)判斷是否是期望出現(xiàn)的類型)
在進(jìn)行第一步之前JSON串對(duì)計(jì)算機(jī)來(lái)說(shuō)只是一串沒(méi)有意義的字符而已。第一步的作用就是把這些無(wú)意義的字符串變成一個(gè)一個(gè)的token,上面我們已經(jīng)為每一種token定義了相應(yīng)的類型和值。所以計(jì)算機(jī)能夠區(qū)分不同的token,并能以token為單位解讀JSON數(shù)據(jù)。
下面我們封裝一個(gè)token類來(lái)存儲(chǔ)每一個(gè)token對(duì)應(yīng)的值
package com.json.demo.tokenizer; /** * 存儲(chǔ)對(duì)應(yīng)類型的字面量 */ public class Token { private TokenType tokenType; private String value; public Token(TokenType tokenType, String value) { this.tokenType = tokenType; this.value = value; } public TokenType getTokenType() { return tokenType; } public void setTokenType(TokenType tokenType) { this.tokenType = tokenType; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { return "Token{" + "tokenType=" + tokenType + ", value="" + value + """ + "}"; } }
在解析的過(guò)程中我們通過(guò)字符流來(lái)不斷的讀取字符,并且需要經(jīng)常根據(jù)相應(yīng)的字符來(lái)判斷狀態(tài)的跳轉(zhuǎn)。所以我們需要自己封裝一個(gè)ReaderChar類,以便我們更好的操作字符流。
package com.json.demo.tokenizer; import java.io.IOException; import java.io.Reader; public class ReaderChar { private static final int BUFFER_SIZE = 1024; private Reader reader; private char[] buffer; private int index; // 下標(biāo) private int size; public ReaderChar(Reader reader) { this.reader = reader; buffer = new char[BUFFER_SIZE]; } /** * 返回 pos 下標(biāo)處的字符,并返回 * @return */ public char peek() { if (index - 1 >= size) { return (char) -1; } return buffer[Math.max(0, index - 1)]; } /** * 返回 pos 下標(biāo)處的字符,并將 pos + 1,最后返回字符 * @return * @throws IOException */ public char next() throws IOException { if (!hasMore()) { return (char) -1; } return buffer[index++]; } /** * 下標(biāo)回退 */ public void back() { index = Math.max(0, --index); } /** * 判斷流是否結(jié)束 */ public boolean hasMore() throws IOException { if (index < size) { return true; } fillBuffer(); return index < size; } /** * 填充buffer數(shù)組 * @throws IOException */ void fillBuffer() throws IOException { int n = reader.read(buffer); if (n == -1) { return; } index = 0; size = n; } }
另外我們還需要一個(gè)TokenList來(lái)存儲(chǔ)解析出來(lái)的token流
package com.json.demo.tokenizer; import java.util.ArrayList; import java.util.List; /** * 存儲(chǔ)詞法解析所得的token流 */ public class TokenList { private Listtokens = new ArrayList (); private int index = 0; public void add(Token token) { tokens.add(token); } public Token peek() { return index < tokens.size() ? tokens.get(index) : null; } public Token peekPrevious() { return index - 1 < 0 ? null : tokens.get(index - 2); } public Token next() { return tokens.get(index++); } public boolean hasMore() { return index < tokens.size(); } @Override public String toString() { return "TokenList{" + "tokens=" + tokens + "}"; } }
JSON解析比其他文本解析要簡(jiǎn)單的地方在于,我們只需要根據(jù)下一個(gè)字符就可知道接下來(lái)它所期望讀取的到的內(nèi)容是什么樣的。如果滿足期望了,則返回 Token,否則返回錯(cuò)誤。
為了方便程序出錯(cuò)時(shí)更好的debug,程序中自定義了兩個(gè)exception類來(lái)處理錯(cuò)誤信息。(具體實(shí)現(xiàn)參考exception包)
下面就是第一步中的重頭戲(核心代碼):
public TokenList getTokenStream(ReaderChar readerChar) throws IOException { this.readerChar = readerChar; tokenList = new TokenList(); // 詞法解析,獲取token流 tokenizer(); return tokenList; } /** * 將JSON文件解析成token流 * @throws IOException */ private void tokenizer() throws IOException { Token token; do { token = start(); tokenList.add(token); } while (token.getTokenType() != TokenType.END_DOCUMENT); } /** * 解析過(guò)程的具體實(shí)現(xiàn)方法 * @return * @throws IOException * @throws JsonParseException */ private Token start() throws IOException, JsonParseException { char ch; while (true){ //先讀一個(gè)字符,若為空白符(ASCII碼在[0, 20H]上)則接著讀,直到剛讀的字符非空白符 if (!readerChar.hasMore()) { return new Token(TokenType.END_DOCUMENT, null); } ch = readerChar.next(); if (!isWhiteSpace(ch)) { break; } } switch (ch) { case "{": return new Token(TokenType.BEGIN_OBJECT, String.valueOf(ch)); case "}": return new Token(TokenType.END_OBJECT, String.valueOf(ch)); case "[": return new Token(TokenType.BEGIN_ARRAY, String.valueOf(ch)); case "]": return new Token(TokenType.END_ARRAY, String.valueOf(ch)); case ",": return new Token(TokenType.SEP_COMMA, String.valueOf(ch)); case ":": return new Token(TokenType.SEP_COLON, String.valueOf(ch)); case "n": return readNull(); case "t": case "f": return readBoolean(); case """: return readString(); case "-": return readNumber(); } if (isDigit(ch)) { return readNumber(); } throw new JsonParseException("Illegal character"); }
在start方法中,我們將每個(gè)處理方法都封裝成了多帶帶的函數(shù)。主要思想就是通過(guò)一個(gè)死循環(huán)不停的讀取字符,然后再根據(jù)字符的期待值,執(zhí)行不同的處理函數(shù)。
下面我們?cè)斀夥治鰩讉€(gè)處理函數(shù):
private Token readString() throws IOException { StringBuilder sb = new StringBuilder(); while(true) { char ch = readerChar.next(); if (ch == "") { // 處理轉(zhuǎn)義字符 if (!isEscape()) { throw new JsonParseException("Invalid escape character"); } sb.append(""); ch = readerChar.peek(); sb.append(ch); if (ch == "u") { // 處理 Unicode 編碼,形如 u4e2d。且只支持 u0000 ~ uFFFF 范圍內(nèi)的編碼 for (int i = 0; i < 4; i++) { ch = readerChar.next(); if (isHex(ch)) { sb.append(ch); } else { throw new JsonParseException("Invalid character"); } } } } else if (ch == """) { // 碰到另一個(gè)雙引號(hào),則認(rèn)為字符串解析結(jié)束,返回 Token return new Token(TokenType.STRING, sb.toString()); } else if (ch == " " || ch == " ") { // 傳入的 JSON 字符串不允許換行 throw new JsonParseException("Invalid character"); } else { sb.append(ch); } } }
該方法也是通過(guò)一個(gè)死循環(huán)來(lái)讀取字符,首先判斷的是JSON中的轉(zhuǎn)義字符。
JSON中允許出現(xiàn)的有以下幾種
" f u four-hex-digits /
具體的處理方法封裝在了isEscape()方法中,處理Unicode 編碼時(shí)要特別注意一下u的后面會(huì)出現(xiàn)四位十六進(jìn)制數(shù)。當(dāng)讀取到一個(gè)雙引號(hào)或者讀取到了非法字符("r"或’、"n")循環(huán)退出。
判斷數(shù)字的時(shí)候也要特別小心,注意負(fù)數(shù),frac,exp等等情況。
通過(guò)上面的解析,我們可以得到一組token,接下來(lái)我們需要以這組token作為輸入,解析出相應(yīng)的JSON對(duì)象
解析出JSON對(duì)象解析之前我們需要定義出JSON對(duì)象(JSONObject)和JSON數(shù)組(JSONArray)的實(shí)體類。
package com.json.demo.jsonstyle; import com.json.demo.exception.JsonTypeException; import com.json.demo.util.FormatUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * JSON的對(duì)象形式 * 對(duì)象是一個(gè)無(wú)序的“‘名稱/值’對(duì)”集合。一個(gè)對(duì)象以“{”(左括號(hào))開(kāi)始,“}”(右括號(hào))結(jié)束。每個(gè)“名稱”后跟一個(gè)“:”(冒號(hào));“‘名稱/值’ 對(duì)”之間使用“,”(逗號(hào))分隔。 */ public class JsonObject { private Mapmap = new HashMap (); public void put(String key, Object value) { map.put(key, value); } public Object get(String key) { return map.get(key); } ... } package com.json.demo.jsonstyle; import com.json.demo.exception.JsonTypeException; import com.json.demo.util.FormatUtil; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * JSON的數(shù)組形式 * 數(shù)組是值(value)的有序集合。一個(gè)數(shù)組以“[”(左中括號(hào))開(kāi)始,“]”(右中括號(hào))結(jié)束。值之間使用“,”(逗號(hào))分隔。 */ public class JsonArray { private List list = new ArrayList(); public void add(Object obj) { list.add(obj); } public Object get(int index) { return list.get(index); } public int size() { return list.size(); } ... }
之后我們就可以寫解析類了,由于代碼較長(zhǎng),這里就不展示了。有興趣的可以去GitHub上下載。實(shí)現(xiàn)邏輯比較簡(jiǎn)單,也易于理解。
解析類中的parse方法首先根據(jù)第一個(gè)token的類型選擇調(diào)用parseJsonObject()或者parseJsonArray(),進(jìn)而返回JSON對(duì)象或者JSON數(shù)組。上面的解析方法中利用位運(yùn)算來(lái)判斷字符的期待值既提高了程序的執(zhí)行效率也有助于提高代碼的ke"du"xi
完成之后我們可以寫一個(gè)測(cè)試類來(lái)驗(yàn)證下我們的解析器的運(yùn)行情況。我們可以自己定義一組JSON串也可以通過(guò)HttpUtil工具類從網(wǎng)上獲取。最后通過(guò)FormatUtil類來(lái)規(guī)范我們輸出。
具體效果如下圖所示:
參考文章http://www.cnblogs.com/absfre...
https://www.liaoxuefeng.com/a...
https://segmentfault.com/a/11...
http://json.org/json-zh.html
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71411.html
摘要:在結(jié)構(gòu)上引入了頭結(jié)點(diǎn)和尾節(jié)點(diǎn),他們分別指向隊(duì)列的頭和尾,嘗試獲取鎖入隊(duì)服務(wù)教程在它提出十多年后的今天,已經(jīng)成為最重要的應(yīng)用技術(shù)之一。隨著編程經(jīng)驗(yàn)的日積月累,越來(lái)越感覺(jué)到了解虛擬機(jī)相關(guān)要領(lǐng)的重要性。 JVM 源碼分析之 Jstat 工具原理完全解讀 http://click.aliyun.com/m/8315/ JVM 源碼分析之 Jstat 工具原理完全解讀 http:...
摘要:夾在中間的被鏈?zhǔn)秸{(diào)用,他們拿到上個(gè)的返回值,為下一個(gè)提供輸入。最終把返回值和傳給。前面我們說(shuō)過(guò),也是一個(gè)模塊,它導(dǎo)出一個(gè)函數(shù),該函數(shù)的參數(shù)是的源模塊,處理后把返回值交給下一個(gè)。 文:小 boy(滬江網(wǎng)校Web前端工程師)本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明作者及出處 showImg(https://segmentfault.com/img/remote/1460000012990131?w=1083...
摘要:安裝僅支持及以上版本。是一個(gè)備用的選項(xiàng),它使得生成的爬蟲(chóng)核心用而非線程池。 如今,網(wǎng)上的爬蟲(chóng)教程可謂是泛濫成災(zāi)了,從urllib開(kāi)始講,最后才講到requests和selenium這類高級(jí)庫(kù),實(shí)際上,根本就不必這么費(fèi)心地去了解這么多無(wú)謂的東西的。只需記住爬蟲(chóng)總共就三大步驟:發(fā)起請(qǐng)求——解析數(shù)據(jù)——存儲(chǔ)數(shù)據(jù),這樣就足以寫出最基本的爬蟲(chóng)了。諸如像Scrapy這樣的框架,可以說(shuō)是集成了爬蟲(chóng)的...
摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開(kāi)源項(xiàng)目了,體現(xiàn)出了社區(qū)對(duì)其的認(rèn)可。監(jiān)聽(tīng)事件手動(dòng)維護(hù)列表這樣我們就簡(jiǎn)單的完成了拖拽排序。 完整項(xiàng)目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺(tái) 系列...
摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開(kāi)源項(xiàng)目了,體現(xiàn)出了社區(qū)對(duì)其的認(rèn)可。監(jiān)聽(tīng)事件手動(dòng)維護(hù)列表這樣我們就簡(jiǎn)單的完成了拖拽排序。 完整項(xiàng)目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺(tái) 系列...
閱讀 4382·2021-11-22 09:34
閱讀 2699·2021-11-12 10:36
閱讀 750·2021-08-18 10:23
閱讀 2648·2019-08-30 15:55
閱讀 3126·2019-08-30 15:53
閱讀 2090·2019-08-30 15:44
閱讀 1369·2019-08-29 15:37
閱讀 1416·2019-08-29 13:04