美文网首页json
一个用Go语言写的高性能的json解析器:GoJay

一个用Go语言写的高性能的json解析器:GoJay

作者: zhoucq | 来源:发表于2018-04-27 13:10 被阅读34次

GoJay是一个用Go语言写的高性能JSON编码/解码工具,本文详细介绍了实现JSON格式编码/解码的结构体代码,以及和其他工具进行性能测试的对比结果。


目前软件包版本为0.9,仍在开发中。
GoJay是用Go语言写的高性能JSON编码/解码工具(目前性能最高,见下面的基准测试)
它有一个简单的API并且不使用反射(reflection)模块。依靠小接口来解码/编码结构和切片。
Gojay还具有强大的流解码功能。

开始


go get github.com/francoispqt/gojay

解码


解码基本结构体的例子:

import  "github.com/francoispqt/gojay" 

type user struct {
 id int
 name string
 email string
}
// implement UnmarshalerObject
func (u *user) UnmarshalObject(dec *gojay.Decoder, key string) error {
  switch key {
  case  "id":
  return dec.AddInt(&u.id)
  case  "name":
  return dec.AddString(&u.name)
  case  "email":
  return dec.AddString(&u.email)
 }
  return  nil
}

func (u *user) NKeys() int {
  return  3
}

func main() {
 u := &user{}
 d := []byte(`{"id":1,"name":"gojay","email":"gojay@email.com"}`)
 err := gojay.UnmarshalObject(d, user)
 if err != nil {
   log.Fatal(err)
 }
}

或者使用解码 API(需要一个io.Reader)

func main() {
 u := &user{}
 dec := gojay.NewDecoder(strings.NewReader(`{"id":1,"name":"gojay","email":"gojay@email.com"}`))
 err := dec.Decode(u)
 if err != nil {
   log.Fatal(err)
 }
}

结构体

UnmarshalerObject接口

要将JSON对象解编(unmarshal)到结构体中,则结构体必须实现UnmarshalerObject接口:

type UnmarshalerObject interface {
 UnmarshalObject(*Decoder, string) error
 NKeys() int
}

UnmarshalObject方法有两个参数,第一个是指向解码器(* gojay.Decoder)的指针,第二个是正在解析的当前键的字符串值。 如果JSON数据不是一个对象,则永远不会调用UnmarshalObject方法。

NKeys方法必须在JSON对象中把key的数量返回给Unmarshal。

具体实现的例子:

type user struct {
 id int
 name string
 email string
}
// implement UnmarshalerObject

func (u *user) UnmarshalObject(dec *gojay.Decoder, key string) error {
  switch k {
  case  "id":
  return dec.AddInt(&u.id)
  case  "name":
  return dec.AddString(&u.name)
  case  "email":
  return dec.AddString(&u.email)
 }
  return  nil
}

func (u *user) NKeys() int {
  return  3
}

数组Array,切片Slice和通道Channel

要将JSON对象解编为切片,数组或通道,必须实现UnmarshalerArray接口:

type UnmarshalerArray  interface {
  UnmarshalArray(*Decoder) error
}

UnmarshalArray方法需要一个参数,一个指向解码器(* gojay.Decoder)的指针。 如果JSON数据不是数组,Unmarshal方法将永远不会被调用。
实现切片的例子:

type testSlice []string
// implement UnmarshalerArray
func (t *testStringArr) UnmarshalArray(dec *gojay.Decoder) error {
 str := ""
 if err := dec.AddString(&str); err != nil {
    return err
 }
 *t = append(*t, str)
  return  nil
}

实现通道的例子:

type ChannelString chan string
// implement UnmarshalerArray
func (c ChannelArray) UnmarshalArray(dec *gojay.Decoder) error {
 str := ""
 if err := dec.AddString(&str); err != nil {
    return err
 }
 c <- str
 return  nil
}

流解码

GoJay自带一个强大的流解码器。
它允许从一个io.Reader流中连续读取并进行JIT解码,将未编组的JSON写入一个通道以允许异步消费。
使用Stream API时,解码器(Decoder)实现context.Context以提供方便优雅的取消。
例子:

type ChannelStream chan *TestObj
// implement UnmarshalerStream
func (c ChannelStream) UnmarshalStream(dec *gojay.StreamDecoder) error {
 obj := &TestObj{}
 if err := dec.AddObject(obj); err != nil {
    return err
 }
 c <- obj
 return  nil
}
func main() {
  // create our channel which will receive our objects
 streamChan := ChannelStream(make(chan *TestObj))
  // get a reader implementing io.Reader
 reader := getAnIOReaderStream()
 dec := gojay.Stream.NewDecoder(reader)
  // start decoding (will block the goroutine until something is written to the ReadWriter)
 go dec.DecodeStream(streamChan)
 for {
  select {
  case v := <-streamChan:
  // do something with my TestObj
  case <-dec.Done():
   os.Exit("finished reading stream")
   }
 }
}

其他类型

要解码其他类型(string,int,int32,int64,uint32,uint64,float,booleans),不需要实现任何接口。
解码字符串的例子:

func main() {
 json := []byte(`"Jay"`)
 var v string
 err := Unmarshal(json, &v)
 if err != nil {
   log.Fatal(err)
 }
 fmt.Println(v) // Jay
}

编码


编码基本结构体的例子:

import  "github.com/francoispqt/gojay"
type user struct {
 id int
 name string
 email string
}
// implement MarshalerObject
func (u *user) MarshalObject(dec *gojay.Decoder, key string) {
 dec.AddIntKey("id", u.id)
 dec.AddStringKey("name", u.name)
 dec.AddStringKey("email", u.email)
}
func (u *user) IsNil() bool {
  return u == nil
}
func main() {
 u := &user{1, "gojay", "gojay@email.com"}
 b, _ := gojay.MarshalObject(user)
 fmt.Println(string(b)) // {"id":1,"name":"gojay","email":"gojay@email.com"}
}

结构体

为了对结构体进行编码,结构体必须实现MarshalerObject接口:

type MarshalerObject  interface {
  MarshalObject(enc *Encoder)
  IsNil() bool
}

MarshalObject方法需要一个参数,一个指向编码器(* gojay.Encoder)的指针。 该方法必须通过调用解码器的方法来添加JSON对象中的所有关键字。
IsNil方法返回一个布尔值,表明接口底层underlying值是否为零。 它用于在不使用反射Reflection的情况下安全地确保底层值不为零。
实现的例子:

type user struct {
 id int
 name string
 email string
}
// implement MarshalerObject
func (u *user) MarshalObject(dec *gojay.Decoder, key string) {
 dec.AddIntKey("id", u.id)
 dec.AddStringKey("name", u.name)
 dec.AddStringKey("email", u.email)
}
func (u *user) IsNil() bool {
  return u == nil
}

数组和切片

要对数组或切片编码,切片/数组必须实现MarshalerArray接口:

type MarshalerArray  interface {
  MarshalArray(enc *Encoder)
}

MarshalArray方法有一个参数,一个指向编码器(* gojay.Encoder)的指针。该方法必须通过调用解码器的方法来添加JSON数组中的所有元素。
实现的例子:

type users []*user
// implement MarshalerArray
func (u *users) MarshalArray(dec *Decoder) error {
  for _, e := range u {
  err := enc.AddObject(e)
  if err != nil {
    return err
   }
 }
  return  nil
}

其他类型

要编码其他类型(string,int,float,booleans),不需要实现任何接口。
字符串编码的例子:

func main() {
 name := "Jay"
 b, err := gojay.Marshal(&name)
 if err != nil {
   log.Fatal(err)
  }
 fmt.Println(string(b)) // "Jay"
}

基准测试


基准测试根据尺寸(小,中,大)对三种不同的数据进行编码和解码。
运行解码器的基准:
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/decoder && make bench

运行编码器的基准:
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench

基准测试结果


解码

1.png

小载荷

基准测试代码
基准测试数据

ns/op纳秒/操作 bytes/op节/操作 allocs/op内存分配/操作
标准库 4661 496 12
JsonParser 1313 0 0
JsonIter 899 192 5
EasyJson 929 240 2
GoJay 662 112 1

中等载荷

基准测试代码
基准测试数据

ns/op纳秒/操作 bytes/op字节/操作 allocs/op内存分配/操作
标准库 30148 2152 496
JsonParser 7793 0 0
EasyJson 7957 232 6
JsonIter 5967 496 44
GoJay 3914 128 7

大载荷

基准测试代码
基准测试数据

ns/op纳秒/操作 bytes/op字节/操作 allocs/op内存分配/操作
EasyJson 106626 160 2
JsonParser 66813 0 0
JsonIter 87994 6738 329
GoJay 43402 1408 76

编码

2.png

小结构体

基准测试代码
基准测试数据

ns/op纳秒/操作 bytes/op字节/操作 allocs/op内存分配/操作
标准库 1280 464 3
EasyJson 871 944 6
JsonIter 866 272 3
GoJay 484 320 2

中等结构体

基准测试代码
基准测试数据

ns/op纳秒/操作 bytes/op字节/操作 allocs/op内存分配/操作
标准库 3325 1496 18
EasyJson 1997 1320 19
JsonIter 1939 648 16
GoJay 1196 936 16

大结构体

基准测试代码
基准测试数据

ns/op纳秒/操作 bytes/op字节/操作 allocs/op内存分配/操作
标准库 51317 28704 326
JsonIter 35247 14608 320
EasyJson 32053 15474 327
GoJay 27847 27888 326

贡献


欢迎贡献您的力量 :)

相关文章

网友评论

    本文标题:一个用Go语言写的高性能的json解析器:GoJay

    本文链接:https://www.haomeiwen.com/subject/mlsblftx.html