Golang 流式解析 Json

json-iterator 庫:https://github.com/json-iterator/gogit

動機

現有的golang解析json的庫都是push模式的,缺乏一種基於pull api的庫。另外就是看一下golang解析json的速度到底如何,還有多少的提升空間。github

API 風格

api 風格上是以 StAX 爲基礎,可是針對 JSON 作了特別的優化。比 StAX 和 SAX 都更簡單可控。固然若是須要最簡單,仍是 DOM 類的 api 最簡單。使用流式pull的api爲的就是最大化控制解析過程。golang

解析 Array

iter := ParseString(`[1,2,3]`)
for iter.ReadArray() {
  iter.ReadUint64()
}

能夠看到,pull api 的風格很是不一樣。整個解析流程是調用者驅動的json

解析 Object

type TestObj struct {
    Field1 string
    Field2 uint64
}
iter := ParseString(`{"field1": "1", "field2": 2}`)
obj := TestObj{}
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
    switch field {
    case "field1":
        obj.Field1 = iter.ReadString()
    case "field2":
        obj.Field2 = iter.ReadUint64()
    default:
        iter.ReportError("bind object", "unexpected field")
    }
}

解析過程不依賴反射,解析出來的值幹什麼事情徹底由你來操縱。你能夠直接作一些累加操做,而不把值先綁定到對象上。api

SKIP

iter := ParseString(`[ {"a" : [{"b": "c"}], "d": 102 }, "b"]`)
iter.ReadArray()
iter.Skip()
iter.ReadArray()
if iter.ReadString() != "b" {
    t.FailNow()
}

對於不關心的字段,能夠選擇跳過。性能優化

性能優化

這個項目的另一個目的是看一下golang原生的json api是快仍是慢,有沒有提升空間。app

基於流解析,無需一次讀到內存裏

// "encoding/json"
func Benchmark_stardard_lib(b *testing.B) {
    b.ReportAllocs()
    for n := 0; n < b.N; n++ {
        file, _ := os.Open("/tmp/large-file.json")
        result := []struct{}{}
        decoder := json.NewDecoder(file)
        decoder.Decode(&result)
        file.Close()
    }
}

5 215547514 ns/op 71467118 B/op 272476 allocs/op性能

// "github.com/json-iterator/go"
func Benchmark_jsoniter(b *testing.B) {
    b.ReportAllocs()
    for n := 0; n < b.N; n++ {
        file, _ := os.Open("/tmp/large-file.json")
        iter := jsoniter.Parse(file, 1024)
        for iter.ReadArray() {
            iter.Skip()
        }
        file.Close()
    }
}

10 110209750 ns/op 4248 B/op 5 allocs/op優化

能夠看到 json iterator 的實現對於內存佔用很是節省。比標準庫的實現快一倍。GC壓力就小更多了。ui

直接解析出 int

對於 int 的解析無需兩遍,一次性讀取。把 ParseInt 的實現合併到 json 解析的代碼裏。

func Benchmark_jsoniter_array(b *testing.B) {
    for n := 0; n < b.N; n++ {
        iter := ParseString(`[1,2,3]`)
        for iter.ReadArray() {
            iter.ReadUint64()
        }
    }
}

10000000 189 ns/op

func Benchmark_json_array(b *testing.B) {
    for n := 0; n < b.N; n++ {
        result := []interface{}{}
        json.Unmarshal([]byte(`[1,2,3]`), &result)
    }
}

1000000 1327 ns/op

這個場景是 7x 的速度

無反射的,有schema的解析

按照schema解析,減小if-else判斷。直接賦值,無需通過反射

type Level1 struct {
    Hello []Level2
}

type Level2 struct {
    World string
}

func Benchmark_jsoniter_nested(b *testing.B) {
    for n := 0; n < b.N; n++ {
        iter := ParseString(`{"hello": [{"world": "value1"}, {"world": "value2"}]}`)
        l1 := Level1{}
        for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
            switch l1Field {
            case "hello":
                l1.Hello = readLevel1Hello(iter)
            default:
                iter.Skip()
            }
        }
    }
}

func readLevel1Hello(iter *Iterator) []Level2 {
    l2Array := make([]Level2, 0, 2)
    for iter.ReadArray() {
        l2 := Level2{}
        for l2Field := iter.ReadObject(); l2Field != ""; l2Field = iter.ReadObject() {
            switch l2Field {
            case "world":
                l2.World = iter.ReadString()
            default:
                iter.Skip()
            }
        }
        l2Array = append(l2Array, l2)
    }
    return l2Array
}

2000000 640 ns/op

func Benchmark_json_nested(b *testing.B) {
    for n := 0; n < b.N; n++ {
        l1 := Level1{}
        json.Unmarshal([]byte(`{"hello": [{"world": "value1"}, {"world": "value2"}]}`), &l1)
    }
}

1000000 1816 ns/op

總結

golang 自帶的 json 庫其實性能很不錯了。根據benchmark(https://github.com/json-itera...)其實速度比其餘的基於流的解析庫還要快(https://github.com/ugorji/go/...)。而這個庫 https://github.com/pquerna/ff... 雖然號稱更快,可是不支持流式解析(要求全部的[]byte都提早讀入到內存裏)。大部分狀況下,就用golang自帶的就足夠好了,別瞎整一些其餘的json解析庫。

若是須要pull api,或者須要額外的2x~6x性能,能夠考慮:https://github.com/json-iterator/go

相關文章
相關標籤/搜索