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

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

贡献


欢迎贡献您的力量 :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容