亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

擼一個JSON解析器

legendaryedu / 1154人閱讀

摘要:姓名張三年齡第二種數(shù)組值的有序列表。姓名張三年齡姓名里斯年齡通過上面的了解可以看出,存在以下幾種數(shù)據(jù)類型以做類比中的中的或中的中的中的或中的解析解析器的基本原理輸入一串字符串,輸出一個對象。

原文地址

JSON

JSON(JavaScript Object Notation, JS 對象簡譜) 是一種輕量級的數(shù)據(jù)交換格式。易于人閱讀和編寫。同時也易于機器解析和生成。采用完全獨立于語言的文本格式,但是也使用了類似于C語言家族的習(xí)慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成為理想的數(shù)據(jù)交換語言。

JSON與JS的區(qū)別以及和XML的區(qū)別具體請參考百度百科

JSON有兩種結(jié)構(gòu):

第一種:對象

“名稱/值”對的集合不同的語言中,它被理解為對象(object),紀(jì)錄(record),結(jié)構(gòu)(struct),字典(dictionary),哈希表(hash table),有鍵列表(keyed list),或者關(guān)聯(lián)數(shù)組 (associative array)。

對象是一個無序的“‘名稱/值’對”集合。一個對象以“{”(左括號)開始,“}”(右括號)結(jié)束。每個“名稱”后跟一個“:”(冒號);“‘名稱/值’ 對”之間使用“,”(逗號)分隔。

{"姓名": "張三", "年齡": "18"}

第二種:數(shù)組

值的有序列表(An ordered list of values)。在大部分語言中,它被理解為數(shù)組(array)。

數(shù)組是值(value)的有序集合。一個數(shù)組以“[”(左中括號)開始,“]”(右中括號)結(jié)束。值之間使用“,”(逗號)分隔。

值(value)可以是雙引號括起來的字符串(string)、數(shù)值(number)、true、false、 null、對象(object)或者數(shù)組(array)。這些結(jié)構(gòu)可以嵌套。

[
    { 
    "姓名": "張三",             
    "年齡":"18"     
    },
             
    {         
    "姓名": "里斯",             
    "年齡":"19"    

    }
]

通過上面的了解可以看出,JSON存在以下幾種數(shù)據(jù)類型(以Java做類比):

json java
string Java中的String
number Java中的Long或Double
true/false Java中的Boolean
null Java中的null
[array] Java中的List或Object[]
{"key":"value"} Java中的Map
解析JSON JSON解析器的基本原理

輸入一串JSON字符串,輸出一個JSON對象。

步驟

JSON解析的過程主要分以下兩步:

第一步:對于輸入的一串JSON字符串我們需要將其解析成一組token流。

例如 JSON字符串{"姓名": "張三", "年齡": "18"} 我們需要將它解析成

{、 姓名、 :、 張三、 ,、 年齡、 :、 18、 }  

這樣一組token流

第二步:根據(jù)得到的token流將其解析成對應(yīng)的JSON對象(JSONObject)或者JSON數(shù)組(JSONArray)

下面我們來詳細(xì)分析下這兩個步驟:

獲取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;    // 每個類型的編號

    TokenType(int code) {
        this.code = code;
    }

    public int getTokenCode() {
        return code;
    }
}

在TokenType中我們?yōu)槊恳环N類型都賦一個數(shù)字,目的是在Parser做一些優(yōu)化操作(通過位運算來判斷是否是期望出現(xiàn)的類型)

在進(jìn)行第一步之前JSON串對計算機來說只是一串沒有意義的字符而已。第一步的作用就是把這些無意義的字符串變成一個一個的token,上面我們已經(jīng)為每一種token定義了相應(yīng)的類型和值。所以計算機能夠區(qū)分不同的token,并能以token為單位解讀JSON數(shù)據(jù)。

下面我們封裝一個token類來存儲每一個token對應(yīng)的值

package com.json.demo.tokenizer;

/**
 * 存儲對應(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 + """ +
                "}";
    }
}

在解析的過程中我們通過字符流來不斷的讀取字符,并且需要經(jīng)常根據(jù)相應(yīng)的字符來判斷狀態(tài)的跳轉(zhuǎn)。所以我們需要自己封裝一個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;
    }
}

另外我們還需要一個TokenList來存儲解析出來的token流

package com.json.demo.tokenizer;

import java.util.ArrayList;
import java.util.List;

/**
 * 存儲詞法解析所得的token流
 */
public class TokenList {
    private List tokens = 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解析比其他文本解析要簡單的地方在于,我們只需要根據(jù)下一個字符就可知道接下來它所期望讀取的到的內(nèi)容是什么樣的。如果滿足期望了,則返回 Token,否則返回錯誤。

為了方便程序出錯時更好的debug,程序中自定義了兩個exception類來處理錯誤信息。(具體實現(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);
    }

    /**
     * 解析過程的具體實現(xiàn)方法
     * @return
     * @throws IOException
     * @throws JsonParseException
     */
    private Token start() throws IOException, JsonParseException {
        char ch;
        while (true){   //先讀一個字符,若為空白符(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方法中,我們將每個處理方法都封裝成了多帶帶的函數(shù)。主要思想就是通過一個死循環(huán)不停的讀取字符,然后再根據(jù)字符的期待值,執(zhí)行不同的處理函數(shù)。

下面我們詳解分析幾個處理函數(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 == """) {     // 碰到另一個雙引號,則認(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);
            }
        }
    }

該方法也是通過一個死循環(huán)來讀取字符,首先判斷的是JSON中的轉(zhuǎn)義字符。

JSON中允許出現(xiàn)的有以下幾種

"


f



	
u four-hex-digits
/

具體的處理方法封裝在了isEscape()方法中,處理Unicode 編碼時要特別注意一下u的后面會出現(xiàn)四位十六進(jìn)制數(shù)。當(dāng)讀取到一個雙引號或者讀取到了非法字符("r"或’、"n")循環(huán)退出。

判斷數(shù)字的時候也要特別小心,注意負(fù)數(shù),frac,exp等等情況。

通過上面的解析,我們可以得到一組token,接下來我們需要以這組token作為輸入,解析出相應(yīng)的JSON對象

解析出JSON對象

解析之前我們需要定義出JSON對象(JSONObject)和JSON數(shù)組(JSONArray)的實體類。

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的對象形式
 * 對象是一個無序的“‘名稱/值’對”集合。一個對象以“{”(左括號)開始,“}”(右括號)結(jié)束。每個“名稱”后跟一個“:”(冒號);“‘名稱/值’ 對”之間使用“,”(逗號)分隔。
 */
public class JsonObject {
    private Map map = 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)的有序集合。一個數(shù)組以“[”(左中括號)開始,“]”(右中括號)結(jié)束。值之間使用“,”(逗號)分隔。
 */
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();
    }
    ...
}

之后我們就可以寫解析類了,由于代碼較長,這里就不展示了。有興趣的可以去GitHub上下載。實現(xiàn)邏輯比較簡單,也易于理解。

解析類中的parse方法首先根據(jù)第一個token的類型選擇調(diào)用parseJsonObject()或者parseJsonArray(),進(jìn)而返回JSON對象或者JSON數(shù)組。上面的解析方法中利用位運算來判斷字符的期待值既提高了程序的執(zhí)行效率也有助于提高代碼的ke"du"xi

完成之后我們可以寫一個測試類來驗證下我們的解析器的運行情況。我們可以自己定義一組JSON串也可以通過HttpUtil工具類從網(wǎng)上獲取。最后通過FormatUtil類來規(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)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/71411.html

相關(guān)文章

  • 后端經(jīng)驗

    摘要:在結(jié)構(gòu)上引入了頭結(jié)點和尾節(jié)點,他們分別指向隊列的頭和尾,嘗試獲取鎖入隊服務(wù)教程在它提出十多年后的今天,已經(jīng)成為最重要的應(yīng)用技術(shù)之一。隨著編程經(jīng)驗的日積月累,越來越感覺到了解虛擬機相關(guān)要領(lǐng)的重要性。 JVM 源碼分析之 Jstat 工具原理完全解讀 http://click.aliyun.com/m/8315/ JVM 源碼分析之 Jstat 工具原理完全解讀 http:...

    i_garfileo 評論0 收藏0
  • 手把手教你一個 Webpack Loader

    摘要:夾在中間的被鏈?zhǔn)秸{(diào)用,他們拿到上個的返回值,為下一個提供輸入。最終把返回值和傳給。前面我們說過,也是一個模塊,它導(dǎo)出一個函數(shù),該函數(shù)的參數(shù)是的源模塊,處理后把返回值交給下一個。 文:小 boy(滬江網(wǎng)校Web前端工程師)本文原創(chuàng),轉(zhuǎn)載請注明作者及出處 showImg(https://segmentfault.com/img/remote/1460000012990131?w=1083...

    sugarmo 評論0 收藏0
  • looter——超輕量級爬蟲框架

    摘要:安裝僅支持及以上版本。是一個備用的選項,它使得生成的爬蟲核心用而非線程池。 如今,網(wǎng)上的爬蟲教程可謂是泛濫成災(zāi)了,從urllib開始講,最后才講到requests和selenium這類高級庫,實際上,根本就不必這么費心地去了解這么多無謂的東西的。只需記住爬蟲總共就三大步驟:發(fā)起請求——解析數(shù)據(jù)——存儲數(shù)據(jù),這樣就足以寫出最基本的爬蟲了。諸如像Scrapy這樣的框架,可以說是集成了爬蟲的...

    impig33 評論0 收藏0
  • 手摸手,帶你用vue后臺 系列三(實戰(zhàn)篇)

    摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開源項目了,體現(xiàn)出了社區(qū)對其的認(rèn)可。監(jiān)聽事件手動維護(hù)列表這樣我們就簡單的完成了拖拽排序。 完整項目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列...

    Channe 評論0 收藏0
  • 手摸手,帶你用vue后臺 系列三(實戰(zhàn)篇)

    摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開源項目了,體現(xiàn)出了社區(qū)對其的認(rèn)可。監(jiān)聽事件手動維護(hù)列表這樣我們就簡單的完成了拖拽排序。 完整項目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列...

    zgbgx 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<