概述
jsoniter(json-iterator)是一款快且靈活的 JSON 解析器替蔬;從 dsljson和 jsonparser 借鑒了大量代碼隔节。
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)蕾管,字符串解析就是性能殺手。我從jsonparser和dsljson學(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源碼