golang第三方類庫(json)-jsoniter

概述

jsoniter(json-iterator)是一款快且靈活的 JSON 解析器;從 dsljsonjsonparser 借鑒了大量代碼。

Jsoniter 有三個(gè)不同的 api 用于不同的場合:

iterator-api:用于處理超大的輸入
bind-api:日常最經(jīng)常使用的對(duì)象綁定
any-api:lazy 解析大對(duì)象,具有 PHP Array 一般的使用體驗(yàn)
一句話總結(jié)就是簡單快捷方便,性能OK!并且完美兼容:encoding/json

性能

性能壓測

對(duì)比

在不使用代碼生成的前提下,Jsoniter 的 Golang 版本可以比標(biāo)準(zhǔn)庫(encoding/json)快 6 倍之多。(提前給個(gè)贊行不行<_>)更多性能

使用

第一步: 引入jsonitor: go get github.com/json-iterator/go

import (
    "fmt"
    "github.com/json-iterator/go"   // 引入
    "os"
    "strings"
)

type ColorGroup struct {
    ID      int
    Name    string
    Colors  []string
}

type Animal struct {
    Name    string
    Order   string
}

func main() {
    // ================= 序列化 =====================
    group := ColorGroup{
        ID:     1,
        Name:   "Reds",
        Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
    }
    b, err := jsoniter.Marshal(group)
    bb, err :=  jsoniter.MarshalIndent(group, "", " ")
    if err != nil{
        fmt.Println("error: ", err)
    }
    os.Stdout.Write(b)
    fmt.Println()
    os.Stdout.Write(bb)
    fmt.Println()

    // ===================  Deconde 解碼 =================
    jsoniter.NewDecoder(os.Stdin).Decode(&group)
    fmt.Println(group)

    //encoder := jsoniter.NewEncoder(os.Stdout)
    //encoder.SetEscapeHTML(true)
    //encoder.Encode(bb)
    //fmt.Println(string(bb))

    // =================== 反序列化 =======================
    var jsonBlob = []byte(`[
        {"Name": "Platypus", "Order": "Monotremata"},
        {"Name": "Quoll",    "Order": "Dasyuromorphia"}
    ]`)
    var animals []Animal
    if err := jsoniter.Unmarshal(jsonBlob, &animals); err != nil{
        fmt.Println("error: ", err)
    }

    fmt.Printf("the unmarshal is  %+v", animals)

    // ======================= 流式 ========================
    fmt.Println()

    // 序列化
    stream := jsoniter.ConfigFastest.BorrowStream(nil)
    defer jsoniter.ConfigFastest.ReturnStream(stream)
    stream.WriteVal(group)
    if stream.Error != nil{
        fmt.Println("error: ", stream.Error)
    }
    os.Stdout.Write(stream.Buffer())

    fmt.Println()
    // 反序列化
    iter := jsoniter.ConfigFastest.BorrowIterator(jsonBlob)
    defer jsoniter.ConfigFastest.ReturnIterator(iter)
    iter.ReadVal(&animals)
    if iter.Error != nil{
        fmt.Println("error: ", iter.Error)
    }
    fmt.Printf("%+v", animals)

    fmt.Println()
    // ====================其他操作===================
    // get
    val := []byte(`{"ID":1,"Name":"Reds","Colors":
{"c":"Crimson","r":"Red","rb":"Ruby","m":"Maroon","tests":["tests_1","tests_2","tests_3","tests_4"]}}`)
    fmt.Println(jsoniter.Get(val, "Colors").ToString())
    fmt.Println("the result is " , jsoniter.Get(val, "Colors","tests",0).ToString())
    // fmt.Println(jsoniter.Get(val, "colors", 0).ToString())

    fmt.Println()
    hello := MyKey("hello")
    output, _ := jsoniter.Marshal(map[*MyKey]string{&hello: "world"})
    fmt.Println(string(output))

    obj := map[*MyKey]string{}
    jsoniter.Unmarshal(output, &obj)
    for k, v := range obj{
        fmt.Println(*k," = ", v)
    }

}
// 自定義類型
// 序列化: 需要實(shí)現(xiàn)MarshellText
type MyKey string

func (m *MyKey) MarshalText() ([]byte, error){
    // return []byte(string(*m)) , nil  // 針對(duì)序列化的內(nèi)容不做任何調(diào)整
    return []byte(strings.Replace(string(*m), "h","H",-1)), nil
}

func(m *MyKey) UnmarshalText(text []byte) error{
    *m = MyKey(text[:])  // 針對(duì)text不做處理
    return nil
}

看到上面的代碼是不是很666?!
若是大文件呢?

// 初始化大文件
func init() {
    ioutil.WriteFile("large-file.json", []byte(`[{
  "person": {
    "id": "d50887ca-a6ce-4e59-b89f-14f0b5d03b03",
    "name": {
      "fullName": "Leonid Bugaev",
      "givenName": "Leonid",
      "familyName": "Bugaev"
    },
    "email": "leonsbox@gmail.com",
    "gender": "male",
    "location": "Saint Petersburg, Saint Petersburg, RU",
    "geo": {
      "city": "Saint Petersburg",
      "state": "Saint Petersburg",
      "country": "Russia",
      "lat": 59.9342802,
      "lng": 30.3350986
    },
    "bio": "Senior engineer at Granify.com",
    "site": "http://flickfaver.com",
    "avatar": "https://d1ts43dypk8bqh.cloudfront.net/v1/avatars/d50887ca-a6ce-4e59-b89f-14f0b5d03b03",
    "employment": {
      "name": "www.latera.ru",
      "title": "Software Engineer",
      "domain": "gmail.com"
    },
    "facebook": {
      "handle": "leonid.bugaev"
    },
    "github": {
      "handle": "buger",
      "id": 14009,
      "avatar": "https://avatars.githubusercontent.com/u/14009?v=3",
      "company": "Granify",
      "blog": "http://leonsbox.com",
      "followers": 95,
      "following": 10
    },
    "twitter": {
      "handle": "flickfaver",
      "id": 77004410,
      "bio": null,
      "followers": 2,
      "following": 1,
      "statuses": 5,
      "favorites": 0,
      "location": "",
      "site": "http://flickfaver.com",
      "avatar": null
    },
    "linkedin": {
      "handle": "in/leonidbugaev"
    },
    "googleplus": {
      "handle": null
    },
    "angellist": {
      "handle": "leonid-bugaev",
      "id": 61541,
      "bio": "Senior engineer at Granify.com",
      "blog": "http://buger.github.com",
      "site": "http://buger.github.com",
      "followers": 41,
      "avatar": "https://d1qb2nb5cznatu.cloudfront.net/users/61541-medium_jpg?1405474390"
    },
    "klout": {
      "handle": null,
      "score": null
    },
    "foursquare": {
      "handle": null
    },
    "aboutme": {
      "handle": "leonid.bugaev",
      "bio": null,
      "avatar": null
    },
    "gravatar": {
      "handle": "buger",
      "urls": [
      ],
      "avatar": "http://1.gravatar.com/avatar/f7c8edd577d13b8930d5522f28123510",
      "avatars": [
        {
          "url": "http://1.gravatar.com/avatar/f7c8edd577d13b8930d5522f28123510",
          "type": "thumbnail"
        }
      ]
    },
    "fuzzy": false
  },
  "company": "hello"
}]`), 0666)
}

/*
200000        8886 ns/op        4336 B/op          6 allocs/op
50000        34244 ns/op        6744 B/op         14 allocs/op
*/
// 解析json大文件
func Benchmark_jsoniter_large_file(b *testing.B) {
    b.ReportAllocs()
    for n := 0; n < b.N; n++ {
        file, _ := os.Open("large-file.json")
        iter := jsoniter.Parse(jsoniter.ConfigDefault, file, 4096)
        count := 0
        iter.ReadArrayCB(func(iter *jsoniter.Iterator) bool {
            // Skip() is strict by default, use --tags jsoniter-sloppy to skip without validation
            iter.Skip()
            count++
            return true
        })
        file.Close()
        if iter.Error != nil {
            b.Error(iter.Error)
        }
    }
}
// 反序列化文件內(nèi)容
func Benchmark_json_large_file(b *testing.B) {
    b.ReportAllocs()
    for n := 0; n < b.N; n++ {
        file, _ := os.Open("large-file.json")
        bytes, _ := ioutil.ReadAll(file)
        file.Close()
        result := []struct{}{}
        err := json.Unmarshal(bytes, &result)
        if err != nil {
            b.Error(err)
        }
    }
}

補(bǔ)充說明:需要定義schema來描述數(shù)據(jù)是一件很麻煩的事情。Jsoniter 允許你把 json 解析為 Any 對(duì)象,然后就可以直接使用了。使用體驗(yàn)和 PHP 的 json_decode 差不多,在Jsoniter中l(wèi)azy解析并不代表慢,在大文件的解析實(shí)例中,凸顯無疑。

不同于其他json包的優(yōu)化點(diǎn)

單次掃描

所有解析都是在字節(jié)數(shù)組流中直接在一次傳遞中完成的。單程有兩個(gè)含義:

  • 在大規(guī)模:迭代器api只是前進(jìn),你從當(dāng)前點(diǎn)獲得你需要的。沒有回頭路。
  • 在微觀尺度上:readInt或readString一次完成。例如,解析整數(shù)不是通過剪切字符串輸出,然后解析字符串。相反,我們使用字節(jié)流直接計(jì)算int值。甚至readFloat或readDouble都以這種方式實(shí)現(xiàn),但有例外。
最小化分配

在所有必要的手段上避免復(fù)制。例如,解析器有一個(gè)內(nèi)部字節(jié)數(shù)組緩沖區(qū),用于保存最近的字節(jié)。解析對(duì)象的字段名稱時(shí),我們不會(huì)分配新字節(jié)來保存字段名稱。相反,如果可能,緩沖區(qū)將重用為切片。
Iterator實(shí)例本身保留了它使用的各種緩沖區(qū)的副本,并且可以通過使用新輸入重置迭代器而不是創(chuàng)建全新迭代器來重用它們。

從stream中拉出來

輸入可以是InputStream或io.Reader,我們不會(huì)將所有字節(jié)讀入大數(shù)組。相反,解析是以塊的形式完成的。當(dāng)我們需要更多時(shí),我們從流中拉出來。

認(rèn)真對(duì)待string

如果處理不當(dāng),字符串解析就是性能殺手。我從jsonparserdsljson學(xué)到的技巧是為沒有轉(zhuǎn)義字符的字符串采取快速路徑。

對(duì)于golang,字符串是utf-8字節(jié)。構(gòu)造字符串的最快方法是從[]byte直接轉(zhuǎn)換為字符串,如果可以確保[]byte不會(huì)消失或被修改。

對(duì)于java,字符串是基于utf-16 char的。將utf8字節(jié)流解析為utf16字符串?dāng)?shù)組由解析器直接完成,而不是使用UTF8字符集。構(gòu)造字符串的成本,簡單地說是一個(gè)char數(shù)組副本。

基于Schema

與tokenizer api相比,Iterator api是活動(dòng)的而不是被動(dòng)的。它不解析令牌,然后分支。相反,在給定模式的情況下,我們確切地知道我們前面有什么,所以我們只是將它們解析為我們認(rèn)為它應(yīng)該是什么。如果輸入不一致,那么我們會(huì)引發(fā)正確的錯(cuò)誤。

跳過不同的路徑

跳過一個(gè)object或array采取不同的路徑是從jsonparser學(xué)到的。當(dāng)我們跳過整個(gè)對(duì)象時(shí),我們不關(guān)心嵌套字段名稱。

表查找

一些計(jì)算,例如char'5'的int值可以提前完成。

其他

綁定到對(duì)象不使用反射api。而是取出原始指針interface{},然后轉(zhuǎn)換為正確的指針類型以設(shè)置值。例如:

*((*int)(ptr)) = iter.ReadInt()

另一個(gè)優(yōu)化是我們知道有多少字段在解析結(jié)構(gòu),所以我們可以用不同的方式編寫字段調(diào)度。對(duì)于沒有領(lǐng)域,我們只是跳過。對(duì)于一個(gè)字段,if / else就足夠了。2~4個(gè)字段切換案例。5個(gè)或更多字段,我們callback使用基于map的字段調(diào)度。

Golang版本沒有使用,go generate因?yàn)槲矣X得它對(duì)新開發(fā)者不友好。我可能會(huì)添加go generate一個(gè)選項(xiàng)并對(duì)后續(xù)的版本進(jìn)行優(yōu)化。它可以更快。由于能夠訪問原始指針,golang數(shù)據(jù)綁定性能已經(jīng)足夠好了。正如我們從基準(zhǔn)測試中看到的那樣,手動(dòng)綁定代碼只是快一點(diǎn)。這種情況可能會(huì)改變,如果golang決定關(guān)閉它的內(nèi)存布局以進(jìn)行直接操作,或者如果我們可以擺脫虛擬方法引入的指針追逐,JIT可以優(yōu)化更多。

后續(xù)

adapter:相當(dāng)于json序列化和反序列化的工具類 直接使用即可通過一行代碼完成相關(guān)的操作
iter: 迭代器的定義 用于json內(nèi)容的解析
stream: 通過流的方式操作json
config: 按需定義了一些默認(rèn)的操作配置類 默認(rèn)已提供多個(gè)config,自己也可以通過jsoniter.Config{CaseSensitive: true}.Froze()定制需要的json API實(shí)例
pool:緩存池 按需緩存不同的實(shí)例對(duì)象 減少內(nèi)存的分配以及資源的占用提高性能
reflect:反射工具類 針對(duì)標(biāo)準(zhǔn)庫中的reflect包的反射相關(guān)接口進(jìn)行優(yōu)化 增強(qiáng)其原有的性能
any:惰性json實(shí)現(xiàn)保持[]byte并延遲解析,把 json 解析為 Any 對(duì)象,然后就可以直接使用了。使用體驗(yàn)和 PHP 的 json_decode 差不多。
jsoniter源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,555評(píng)論 19 139
  • 早上窩在床上裝死的室友,在我思考今天的推文寫什么的時(shí)候放了一首歌,《不說》,李榮浩。就這樣,決定了此篇文章的主題。...
    熊貓微刊閱讀 409評(píng)論 0 4
  • 有的人可以同時(shí)干幾件事,有的人一段時(shí)間內(nèi)只能專注幾件事,甚至只能做一件事。他們可以比較出好壞嗎?這些現(xiàn)象說明了什么...
    一抽屜松露閱讀 654評(píng)論 0 0
  • 昨晚微信公眾號(hào)發(fā)出一篇文章后,有朋友開玩笑,說“發(fā)得和咪蒙一樣晚”,又有朋友說“這才專業(yè),這時(shí)候是自媒體閱讀率最高...
    53953a9a18b7閱讀 272評(píng)論 0 0
  • 孤獨(dú)是童年時(shí) 在山上發(fā)現(xiàn)了一棵成熟的櫻桃樹 卻找不到一起品嘗的朋友 只好自己爬上樹 一個(gè)人吃了個(gè)飽 孤獨(dú)是年青時(shí) ...
    欒語閱讀 211評(píng)論 0 0

友情鏈接更多精彩內(nèi)容