一個(gè)用Go語(yǔ)言寫的高性能的json解析器:GoJay

GoJay是一個(gè)用Go語(yǔ)言寫的高性能JSON編碼/解碼工具,本文詳細(xì)介紹了實(shí)現(xiàn)JSON格式編碼/解碼的結(jié)構(gòu)體代碼喜命,以及和其他工具進(jìn)行性能測(cè)試的對(duì)比結(jié)果沟沙。


目前軟件包版本為0.9,仍在開發(fā)中渊抄。
GoJay是用Go語(yǔ)言寫的高性能JSON編碼/解碼工具(目前性能最高尝胆,見下面的基準(zhǔn)測(cè)試)
它有一個(gè)簡(jiǎn)單的API并且不使用反射(reflection)模塊。依靠小接口來(lái)解碼/編碼結(jié)構(gòu)和切片护桦。
Gojay還具有強(qiáng)大的流解碼功能含衔。

開始


go get github.com/francoispqt/gojay

解碼


解碼基本結(jié)構(gòu)體的例子:

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(需要一個(gè)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)
 }
}

結(jié)構(gòu)體

UnmarshalerObject接口

要將JSON對(duì)象解編(unmarshal)到結(jié)構(gòu)體中,則結(jié)構(gòu)體必須實(shí)現(xiàn)UnmarshalerObject接口:

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

UnmarshalObject方法有兩個(gè)參數(shù)二庵,第一個(gè)是指向解碼器(* gojay.Decoder)的指針贪染,第二個(gè)是正在解析的當(dāng)前鍵的字符串值。 如果JSON數(shù)據(jù)不是一個(gè)對(duì)象催享,則永遠(yuǎn)不會(huì)調(diào)用UnmarshalObject方法杭隙。

NKeys方法必須在JSON對(duì)象中把key的數(shù)量返回給Unmarshal。

具體實(shí)現(xiàn)的例子:

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
}

數(shù)組Array因妙,切片Slice和通道Channel

要將JSON對(duì)象解編為切片痰憎,數(shù)組或通道,必須實(shí)現(xiàn)UnmarshalerArray接口:

type UnmarshalerArray  interface {
  UnmarshalArray(*Decoder) error
}

UnmarshalArray方法需要一個(gè)參數(shù)攀涵,一個(gè)指向解碼器(* gojay.Decoder)的指針铣耘。 如果JSON數(shù)據(jù)不是數(shù)組,Unmarshal方法將永遠(yuǎn)不會(huì)被調(diào)用以故。
實(shí)現(xiàn)切片的例子:

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
}

實(shí)現(xiàn)通道的例子:

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自帶一個(gè)強(qiáng)大的流解碼器蜗细。
它允許從一個(gè)io.Reader流中連續(xù)讀取并進(jìn)行JIT解碼,將未編組的JSON寫入一個(gè)通道以允許異步消費(fèi)怒详。
使用Stream API時(shí)炉媒,解碼器(Decoder)實(shí)現(xiàn)context.Context以提供方便優(yōu)雅的取消。
例子:

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)蜗元,不需要實(shí)現(xiàn)任何接口或渤。
解碼字符串的例子:

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

編碼


編碼基本結(jié)構(gòu)體的例子:

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"}
}

結(jié)構(gòu)體

為了對(duì)結(jié)構(gòu)體進(jìn)行編碼,結(jié)構(gòu)體必須實(shí)現(xiàn)MarshalerObject接口:

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

MarshalObject方法需要一個(gè)參數(shù)奕扣,一個(gè)指向編碼器(* gojay.Encoder)的指針薪鹦。 該方法必須通過(guò)調(diào)用解碼器的方法來(lái)添加JSON對(duì)象中的所有關(guān)鍵字。
IsNil方法返回一個(gè)布爾值惯豆,表明接口底層underlying值是否為零池磁。 它用于在不使用反射Reflection的情況下安全地確保底層值不為零。
實(shí)現(xiàn)的例子:

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
}

數(shù)組和切片

要對(duì)數(shù)組或切片編碼楷兽,切片/數(shù)組必須實(shí)現(xiàn)MarshalerArray接口:

type MarshalerArray  interface {
  MarshalArray(enc *Encoder)
}

MarshalArray方法有一個(gè)參數(shù)地熄,一個(gè)指向編碼器(* gojay.Encoder)的指針。該方法必須通過(guò)調(diào)用解碼器的方法來(lái)添加JSON數(shù)組中的所有元素芯杀。
實(shí)現(xiàn)的例子:

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)却特,不需要實(shí)現(xiàn)任何接口。
字符串編碼的例子:

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

基準(zhǔn)測(cè)試


基準(zhǔn)測(cè)試根據(jù)尺寸(小筛圆,中裂明,大)對(duì)三種不同的數(shù)據(jù)進(jìn)行編碼和解碼。
運(yùn)行解碼器的基準(zhǔn):
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/decoder && make bench

運(yùn)行編碼器的基準(zhǔn):
cd $GOPATH/src/github.com/francoispqt/gojay/benchmarks/encoder && make bench

基準(zhǔn)測(cè)試結(jié)果


解碼

1.png

小載荷

基準(zhǔn)測(cè)試代碼
基準(zhǔn)測(cè)試數(shù)據(jù)

ns/op納秒/操作 bytes/op節(jié)/操作 allocs/op內(nèi)存分配/操作
標(biāo)準(zhǔn)庫(kù) 4661 496 12
JsonParser 1313 0 0
JsonIter 899 192 5
EasyJson 929 240 2
GoJay 662 112 1

中等載荷

基準(zhǔn)測(cè)試代碼
基準(zhǔn)測(cè)試數(shù)據(jù)

ns/op納秒/操作 bytes/op字節(jié)/操作 allocs/op內(nèi)存分配/操作
標(biāo)準(zhǔn)庫(kù) 30148 2152 496
JsonParser 7793 0 0
EasyJson 7957 232 6
JsonIter 5967 496 44
GoJay 3914 128 7

大載荷

基準(zhǔn)測(cè)試代碼
基準(zhǔn)測(cè)試數(shù)據(jù)

ns/op納秒/操作 bytes/op字節(jié)/操作 allocs/op內(nèi)存分配/操作
EasyJson 106626 160 2
JsonParser 66813 0 0
JsonIter 87994 6738 329
GoJay 43402 1408 76

編碼

2.png

小結(jié)構(gòu)體

基準(zhǔn)測(cè)試代碼
基準(zhǔn)測(cè)試數(shù)據(jù)

ns/op納秒/操作 bytes/op字節(jié)/操作 allocs/op內(nèi)存分配/操作
標(biāo)準(zhǔn)庫(kù) 1280 464 3
EasyJson 871 944 6
JsonIter 866 272 3
GoJay 484 320 2

中等結(jié)構(gòu)體

基準(zhǔn)測(cè)試代碼
基準(zhǔn)測(cè)試數(shù)據(jù)

ns/op納秒/操作 bytes/op字節(jié)/操作 allocs/op內(nèi)存分配/操作
標(biāo)準(zhǔn)庫(kù) 3325 1496 18
EasyJson 1997 1320 19
JsonIter 1939 648 16
GoJay 1196 936 16

大結(jié)構(gòu)體

基準(zhǔn)測(cè)試代碼
基準(zhǔn)測(cè)試數(shù)據(jù)

ns/op納秒/操作 bytes/op字節(jié)/操作 allocs/op內(nèi)存分配/操作
標(biāo)準(zhǔn)庫(kù) 51317 28704 326
JsonIter 35247 14608 320
EasyJson 32053 15474 327
GoJay 27847 27888 326

貢獻(xiàn)


歡迎貢獻(xiàn)您的力量 :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末太援,一起剝皮案震驚了整個(gè)濱河市闽晦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌提岔,老刑警劉巖尼荆,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異唧垦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)液样,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門振亮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鞭莽,你說(shuō)我怎么就攤上這事坊秸。” “怎么了澎怒?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵褒搔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)星瘾,這世上最難降的妖魔是什么走孽? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮琳状,結(jié)果婚禮上磕瓷,老公的妹妹穿的比我還像新娘。我一直安慰自己念逞,他們只是感情好困食,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翎承,像睡著了一般硕盹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叨咖,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天瘩例,我揣著相機(jī)與錄音,去河邊找鬼芒澜。 笑死仰剿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痴晦。 我是一名探鬼主播南吮,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼誊酌!你這毒婦竟也來(lái)了部凑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤碧浊,失蹤者是張志新(化名)和其女友劉穎涂邀,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箱锐,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡比勉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驹止。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浩聋。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臊恋,靈堂內(nèi)的尸體忽然破棺而出衣洁,到底是詐尸還是另有隱情,我是刑警寧澤抖仅,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布坊夫,位于F島的核電站砖第,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏环凿。R本人自食惡果不足惜梧兼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拷邢。 院中可真熱鬧袱院,春花似錦、人聲如沸瞭稼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)环肘。三九已至欲虚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悔雹,已是汗流浹背复哆。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腌零,地道東北人梯找。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像益涧,于是被迫代替她去往敵國(guó)和親锈锤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容