深入 Go 中各个高性能 JSON 解析库
转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/archives/535
其实本来我是没打算去看 JSON 库的性能问题的,但是最近我对我的项目做了一次 pprof,从下面的火焰图中可以发现在业务逻辑处理中,有一半多的性能消耗都是在 JSON 解析过程中,所以就有了这篇文章。
这篇文章深入源码分析一下在 Go 中标准库是如何解析 JSON 的,然后再看看有哪些比较流行的 Json 解析库,以及这些库都有什么特点,在什么场景下能更好的帮助我们进行开发。
主要介绍分析以下几个库:
库名 | Star |
---|---|
标准库 JSON Unmarshal | |
valyala/fastjson | 1.2 k |
tidwall/gjson | 8.3 k |
buger/jsonparser | 4 k |
json-iterator
库也是一个非常有名的库,但是我测了一下性能和标准库相差很小,相比之下还是标准库更值得使用;
Jeffail/gabs
库与 bitly/go-simplejson
直接用的标准库的 Unmarshal 来进行解析,所以性能上和标准库一致,本篇文章也不会提及;
easyjson
这个库需要像 protobuf 一样为每一个结构体生成序列化的代码,具有强入侵性,我个人不是很喜欢,所以也没提及。
上面的这些库是我能搜到的 Star 数大于 1k 比较知名,并且仍然在迭代的 JSON 解析库,如果有遗漏的,可以联系我,我会补上。
标准库 JSON Unmarshal
分析
func Unmarshal(data []byte, v interface{
})
官方的 JSON 解析库需要传两个参数,一个是需要被序列化的对象,另一个是表示这个对象的类型。
在真正执行 JSON 解析之前会调用 reflect.ValueOf
来获取参数 v 的反射对象。然后会获取到传入的 data 对象的开头非空字符来界定该用哪种方式来进行解析。
func (d *decodeState) value(v reflect.Value) error {
switch d.opcode {
default:
panic(phasePanicMsg)
// 数组
case scanBeginArray:
...
// 结构体或map
case scanBeginObject:
...
// 字面量,包括 int、string、float 等
case scanBeginLiteral:
...
}
return nil
}
如果被解析的对象是以[
开头,那么表示这是个数组对象会进入到 scanBeginArray 分支;如果是以{
开头,表明被解析的对象是一个结构体或 map,那么进入到 scanBeginObject 分支 等等。
以解析对象为例:
func (d *decodeState) object(v reflect.Value) error {
...
var fields structFields
// 检验这个对象的类型是 map 还是 结构体
switch v.Kind() {
case reflect.Map:
...
case reflect.Struct:
// 缓存结构体的字段到 fields 对象中
fields = cachedTypeFields(t)
// ok
default:
d.saveError(&UnmarshalTypeError{
Value: "object", Type: t, Offset: int64(d.off)})
d.skip()
return nil
}
var mapElem reflect.Value
origErrorContext := d.errorContext
// 循环一个个解析JSON字符串中的 key value 值
for {
start := d.readIndex()
d.rescanLiteral()
item := d.data[start:d.readIndex()]
// 获取 key 值
key, ok := unquoteBytes(item)
if !ok {
panic(phasePanicMsg)
}
var subv reflect.Value
destring := false
...
// 根据 value 的类型反射设置 value 值
if destring {
// value 值是字面量会进入到这里
switch qv := d.valueQuoted().(type) {
case nil:
if err := d.literalStore(nullLiteral, subv, false); err != nil {
return err
}
case string:
if err := d.literalStore([]byte(qv), subv, true); err != nil {
return err
}
default:
d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type()))
}
} else {
// 数组或对象会递归调用 value 方法
if err := d.value(subv); err !=