golang第三方类库(json)-jsoniter

神奇的考拉 · · 8114 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

概述

jsoniter(json-iterator)是一款快且灵活的 JSON 解析器;从 dsljsonjsonparser 借鉴了大量代码。

Jsoniter 有三个不同的 api 用于不同的场合:

iterator-api:用于处理超大的输入
bind-api:日常最经常使用的对象绑定
any-api:lazy 解析大对象,具有 PHP Array 一般的使用体验
一句话总结就是简单快捷方便,性能OK!并且完美兼容:encoding/json

性能

性能压测

对比

在不使用代码生成的前提下,Jsoniter 的 Golang 版本可以比标准库(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)
    }

}
// 自定义类型
// 序列化: 需要实现MarshellText
type MyKey string

func (m *MyKey) MarshalText() ([]byte, error){
    // return []byte(string(*m)) , nil  // 针对序列化的内容不做任何调整
    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)
        }
    }
}
// 反序列化文件内容
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来描述数据是一件很麻烦的事情。Jsoniter 允许你把 json 解析为 Any 对象,然后就可以直接使用了。使用体验和 PHP 的 json_decode 差不多,在Jsoniter中lazy解析并不代表慢,在大文件的解析实例中,凸显无疑。

不同于其他json包的优化点

单次扫描

所有解析都是在字节数组流中直接在一次传递中完成的。单程有两个含义:

  • 在大规模:迭代器api只是前进,你从当前点获得你需要的。没有回头路。
  • 在微观尺度上:readInt或readString一次完成。例如,解析整数不是通过剪切字符串输出,然后解析字符串。相反,我们使用字节流直接计算int值。甚至readFloat或readDouble都以这种方式实现,但有例外。
最小化分配

在所有必要的手段上避免复制。例如,解析器有一个内部字节数组缓冲区,用于保存最近的字节。解析对象的字段名称时,我们不会分配新字节来保存字段名称。相反,如果可能,缓冲区将重用为切片。
Iterator实例本身保留了它使用的各种缓冲区的副本,并且可以通过使用新输入重置迭代器而不是创建全新迭代器来重用它们。

从stream中拉出来

输入可以是InputStream或io.Reader,我们不会将所有字节读入大数组。相反,解析是以块的形式完成的。当我们需要更多时,我们从流中拉出来。

认真对待string

如果处理不当,字符串解析就是性能杀手。我从jsonparserdsljson学到的技巧是为没有转义字符的字符串采取快速路径。

对于golang,字符串是utf-8字节。构造字符串的最快方法是从[]byte直接转换为字符串,如果可以确保[]byte不会消失或被修改。

对于java,字符串是基于utf-16 char的。将utf8字节流解析为utf16字符串数组由解析器直接完成,而不是使用UTF8字符集。构造字符串的成本,简单地说是一个char数组副本。

基于Schema

与tokenizer api相比,Iterator api是活动的而不是被动的。它不解析令牌,然后分支。相反,在给定模式的情况下,我们确切地知道我们前面有什么,所以我们只是将它们解析为我们认为它应该是什么。如果输入不一致,那么我们会引发正确的错误。

跳过不同的路径

跳过一个object或array采取不同的路径是从jsonparser学到的。当我们跳过整个对象时,我们不关心嵌套字段名称。

表查找

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

其他

绑定到对象不使用反射api。而是取出原始指针interface{},然后转换为正确的指针类型以设置值。例如:

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

另一个优化是我们知道有多少字段在解析结构,所以我们可以用不同的方式编写字段调度。对于没有领域,我们只是跳过。对于一个字段,if / else就足够了。2~4个字段切换案例。5个或更多字段,我们callback使用基于map的字段调度。

Golang版本没有使用,go generate因为我觉得它对新开发者不友好。我可能会添加go generate一个选项并对后续的版本进行优化。它可以更快。由于能够访问原始指针,golang数据绑定性能已经足够好了。正如我们从基准测试中看到的那样,手动绑定代码只是快一点。这种情况可能会改变,如果golang决定关闭它的内存布局以进行直接操作,或者如果我们可以摆脱虚拟方法引入的指针追逐,JIT可以优化更多。

后续

adapter:相当于json序列化和反序列化的工具类 直接使用即可通过一行代码完成相关的操作
iter: 迭代器的定义 用于json内容的解析
stream: 通过流的方式操作json
config: 按需定义了一些默认的操作配置类 默认已提供多个config,自己也可以通过jsoniter.Config{CaseSensitive: true}.Froze()定制需要的json API实例
pool:缓存池 按需缓存不同的实例对象 减少内存的分配以及资源的占用提高性能
reflect:反射工具类 针对标准库中的reflect包的反射相关接口进行优化 增强其原有的性能
any:惰性json实现保持[]byte并延迟解析,把 json 解析为 Any 对象,然后就可以直接使用了。使用体验和 PHP 的 json_decode 差不多。
jsoniter源码


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:神奇的考拉

查看原文:golang第三方类库(json)-jsoniter

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

8114 次点击  
加入收藏 微博
上一篇:Go get被墙问题
下一篇:golang实现AVL树
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传