代码改变世界

Golang Gob编码

2012-08-24 09:47  轩脉刃  阅读(36599)  评论(3编辑  收藏  举报

gob是Golang包自带的一个数据结构序列化的编码/解码工具。编码使用Encoder,解码使用Decoder。一种典型的应用场景就是RPC(remote procedure calls)。

gob和json的pack之类的方法一样,由发送端使用Encoder对数据结构进行编码。在接收端收到消息之后,接收端使用Decoder将序列化的数据变化成本地变量。

 

有一点需要注意,

发送方的结构和接受方的结构并不需要完全一致

结构体中缺省的字段将不会被发送。而且在接收方,并不需要所有的字段都要有对应的结构属性对应。godoc中的这个例子很形象:

clip_image001

当发送方传递的是struct{A, B int}结构的值的时候,接收方可以允许前9种结构,但是后面4种结构确实不允许的。

 

个人觉得这种设定是很符合逻辑的:接收端只接受和发送数据“相似”的数据结构。允许模拟相似,但是不允许矛盾。

 

各个类型的编解码规则

整型:分为sign int和usign int, 其中从上面例子也看到,int和uint是不能互相编解码的。float和int也是不能互相编解码的。

Struct,array,slice是可以被编码的。但是function和channel是不能被编码的。

bool类型是被当作uint来编码的,0是false,1是true。

浮点类型的值都是被当作float64类型的值来编码的

String和[]byte传递是uint(byte个数) + byte[]的形式编码的

Slice和array是按照uint(array个数) + 每个array编码 这样的形式进行编码的

Maps是按照 uint(Map个数) + 键值对 这样的形式进行编码的

 

Struct是按照一对对(属性名 + 属性值)来进行编码的。其中属性值是其自己对应的gob编码。前面说过,如果有一个属性值为0或空,则这个属性直接被忽略。每个属性的序号是由编码时候顺序决定的,从0开始顺序递增。Struct在序列化前会以-1代表序列化的开始,以0代表序列化结束。即Struct的序列化是按照 “-1 (0 属性1名字 属性1值) (1 属性2名字 属性2值) 0 ”来进行编码的。

 

非常重要的一点:

Struct中的属性应该是public的,即应该是大写字母开头。

这样才能被包外的函数访问!!(谢谢TreapDB提醒)

Gob提供的函数

clip_image002

 

Encode和Decode

对于Encoder和Decoder可以看这个例子:

package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

type P struct {
	X, Y, Z int
	Name    string
}

type Q struct {
	X, Y *int32
	Name string
}

func main() {
	var network bytes.Buffer        
	enc := gob.NewEncoder(&network) 
	dec := gob.NewDecoder(&network) 
	// Encode (send) the value.
	err := enc.Encode(P{3, 4, 5, "Pythagoras"})
	if err != nil {
		log.Fatal("encode error:", err)
	}
	// Decode (receive) the value.
	var q Q
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decode error:", err)
	}
	fmt.Println(q)
	fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)

}

所有Encoder和Decoder的构造函数都有一个io结构,需要制定你将使用哪个io进行编码解码的传输。

这个代码要注意几个地方:

1 P和Q是两个结构体,应该说是“相似”的两个结构体

2 Encode是将结构体传递过来,但是Decode的函数参数却是一个pointer!

这点在godoc中有说:

f e is nil, the value will be discarded. Otherwise, the value underlying e must be a pointer to the correct type for the next data item received.

Decode的参数如果不是nil,那就一定是一个指针了。

3 如果你将Encode传入一个pointer,即

func main() {
	var network bytes.Buffer        // Stand-in for a network connection
	enc := gob.NewEncoder(&network) // Will write to network.
	dec := gob.NewDecoder(&network) // Will read from network.
	// Encode (send) the value.
	err := enc.Encode(&P{3, 4, 5, "Pythagoras"})
	if err != nil {
		log.Fatal("encode error:", err)
	}
	// Decode (receive) the value.
	var q Q
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decode error:", err)
	}
	fmt.Println(q)
	fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)


}

这个function也是没有问题的。

Register和RegisterName

这两个方法是当编解码中有一个字段是interface{}的时候需要对interface{}的可能产生的类型进行注册。具体就看一下下面这个例子:

package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

type P struct {
	X, Y, Z int
	Name    interface{}
}

type Q struct {
	X, Y *int32
	Name interface{}
}

type Inner struct {
	Test int
}

func main() {
	var network bytes.Buffer        // Stand-in for a network connection
	enc := gob.NewEncoder(&network) // Will write to network.
	dec := gob.NewDecoder(&network) // Will read from network.
	
	gob.Register(Inner{})
	
	// Encode (send) the value.
	inner := Inner{1}
	err := enc.Encode(P{1,2,3, inner})
	if err != nil {
		log.Fatal("encode error:", err)
	}
	// Decode (receive) the value.
	var q Q
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decode error:", err)
	}
	fmt.Println(q)
	fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)


}

这里使用了gob.Register(Inner{})告诉系统:所有的Interface是有可能为Inner结构的。

在这个例子中,如果你注释了gob.Register, 系统会报错。

RegisterName是和Register一样的效果,只是在Register的同时也为这个类型附上一个别名。

 

GebEncoder和GobDecoder

这是两个接口,如果你的数据结构实现了这两个接口,当调用encoder.Encode和decoder.Decode的时候就会调用这两个结构的对应函数

看一下下面这个例子:

package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

type P struct {
	X, Y, Z int
	Name    string
}

func (this *P)GobEncode() ([]byte, error) {
    return []byte{},nil 
}

type Q struct {
	X, Y *int32
	Name string
}

func main() {
	var network bytes.Buffer        
	enc := gob.NewEncoder(&network) 
	dec := gob.NewDecoder(&network) 
	// Encode (send) the value.
	err := enc.Encode(P{3, 4, 5, "Pythagoras"})
	if err != nil {
		log.Fatal("encode error:", err)
	}
	// Decode (receive) the value.
	var q Q
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decode error:", err)
	}
	fmt.Println(q)
	fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)

}

这里我的P实现了GobEncoder接口,因此在enc.Encode的时候会调用func (this *P)GobEncode() ([]byte, error)

当然我这个函数直接返回的是空byte,因此在解码的时候会报错:decode error:gob: type mismatch in decoder: want struct type main.Q; got non-struct

这两个接口暴露出来就代表你为自己定义的结构进行编解码规则制定。当然,如果使用自己的编解码规则,在编码和解码的过程就需要是一对的

后记

gob包是golang提供的“私有”的编解码方式,文档中也说了它的效率会比json,xml等更高(虽然我也没有验证)。因此在两个Go 服务之间的相互通信建议不要再使用json传递了,完全可以直接使用gob来进行数据传递。

参考资料

http://blog.golang.org/2011/03/gobs-of-data.html

http://www.mikespook.com/2011/03/%E3%80%90%E7%BF%BB%E8%AF%91%E3%80%91gob-%E7%9A%84%E6%95%B0%E6%8D%AE/