概述
jsoniter(json-iterator)是一款快且靈活的 JSON 解析器;從 dsljson和 jsonparser 借鑒了大量代碼。
Jsoniter 有三個(gè)不同的 api 用于不同的場合:
iterator-api:用于處理超大的輸入
bind-api:日常最經(jīng)常使用的對(duì)象綁定
any-api:lazy 解析大對(duì)象,具有 PHP Array 一般的使用體驗(yàn)
一句話總結(jié)就是簡單快捷方便,性能OK!并且完美兼容:encoding/json
性能


在不使用代碼生成的前提下,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),字符串解析就是性能殺手。我從jsonparser和dsljson學(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源碼