golang第三方類庫(json)-jsoniter

概述

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

Jsoniter 有三個不同的 api 用于不同的場合:

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

性能

性能壓測

對比

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

使用

第一步: 引入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)
    }

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

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

func(m *MyKey) UnmarshalText(text []byte) error{
    *m = MyKey(text[:])  // 針對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)
        }
    }
}

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

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

單次掃描

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

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

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

從stream中拉出來

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

認(rèn)真對待string

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

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

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

基于Schema

與tokenizer api相比聚谁,Iterator api是活動的而不是被動的冻晤。它不解析令牌漱办,然后分支这刷。相反,在給定模式的情況下娩井,我們確切地知道我們前面有什么暇屋,所以我們只是將它們解析為我們認(rèn)為它應(yīng)該是什么。如果輸入不一致洞辣,那么我們會引發(fā)正確的錯誤咐刨。

跳過不同的路徑

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

表查找

一些計算,例如char'5'的int值可以提前完成著瓶。

其他

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

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

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

Golang版本沒有使用卵慰,go generate因為我覺得它對新開發(fā)者不友好。我可能會添加go generate一個選項并對后續(xù)的版本進(jìn)行優(yōu)化佛呻。它可以更快裳朋。由于能夠訪問原始指針,golang數(shù)據(jù)綁定性能已經(jīng)足夠好了吓著。正如我們從基準(zhǔn)測試中看到的那樣鲤嫡,手動綁定代碼只是快一點。這種情況可能會改變绑莺,如果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)已提供多個config诫肠,自己也可以通過jsoniter.Config{CaseSensitive: true}.Froze()定制需要的json API實例
pool:緩存池 按需緩存不同的實例對象 減少內(nèi)存的分配以及資源的占用提高性能
reflect:反射工具類 針對標(biāo)準(zhǔn)庫中的reflect包的反射相關(guān)接口進(jìn)行優(yōu)化 增強其原有的性能
any:惰性json實現(xiàn)保持[]byte并延遲解析司澎,把 json 解析為 Any 對象,然后就可以直接使用了栋豫。使用體驗和 PHP 的 json_decode 差不多挤安。
jsoniter源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市丧鸯,隨后出現(xiàn)的幾起案子蛤铜,更是在濱河造成了極大的恐慌,老刑警劉巖丛肢,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件围肥,死亡現(xiàn)場離奇詭異,居然都是意外死亡蜂怎,警方通過查閱死者的電腦和手機虐先,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來派敷,“玉大人,你說我怎么就攤上這事撰洗±河洌” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵差导,是天一觀的道長试躏。 經(jīng)常有香客問我,道長设褐,這世上最難降的妖魔是什么颠蕴? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮助析,結(jié)果婚禮上犀被,老公的妹妹穿的比我還像新娘。我一直安慰自己外冀,他們只是感情好寡键,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著雪隧,像睡著了一般西轩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脑沿,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天藕畔,我揣著相機與錄音,去河邊找鬼庄拇。 笑死注服,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祠汇,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼仍秤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了可很?” 一聲冷哼從身側(cè)響起诗力,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎我抠,沒想到半個月后苇本,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡菜拓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年瓣窄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纳鼎。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡俺夕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贱鄙,到底是詐尸還是另有隱情劝贸,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布逗宁,位于F島的核電站映九,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瞎颗。R本人自食惡果不足惜件甥,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哼拔。 院中可真熱鬧引有,春花似錦、人聲如沸管挟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僻孝。三九已至导帝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間穿铆,已是汗流浹背您单。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荞雏,地道東北人虐秦。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓平酿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親悦陋。 傳聞我的和親對象是個殘疾皇子蜈彼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

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