這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
gob是Golang包內建的一個資料結構序列化的編碼/解碼工具。編碼使用Encoder,解碼使用Decoder。一種典型的應用情境就是RPC(remote procedure calls)。
gob和json的pack之類的方法一樣,由發送端使用Encoder對資料結構進行編碼。在接收端收到訊息之後,接收端使用Decoder將序列化的資料變化成本地變數。
有一點需要注意,
發送方的結構和接受方的結構並不需要完全一致
結構體中預設的欄位將不會被發送。而且在接收方,並不需要所有的欄位都要有對應的結構屬性對應。godoc中的這個例子很形象:
當發送方傳遞的是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提供的函數
Encode和Decode
對於Encoder和Decoder可以看這個例子:
package mainimport ("bytes""encoding/gob""fmt""log")type P struct {X, Y, Z intName string}type Q struct {X, Y *int32Name 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 Qerr = 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 connectionenc := 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 Qerr = 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 mainimport ("bytes""encoding/gob""fmt""log")type P struct {X, Y, Z intName interface{}}type Q struct {X, Y *int32Name interface{}}type Inner struct {Test int}func main() {var network bytes.Buffer // Stand-in for a network connectionenc := 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 Qerr = 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 mainimport ("bytes""encoding/gob""fmt""log")type P struct {X, Y, Z intName string}func (this *P)GobEncode() ([]byte, error) { return []byte{},nil }type Q struct {X, Y *int32Name 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 Qerr = 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/