Golang: Encoding: gob

 13th January 2022 at 3:23pm

Golang 内置的 encoding/gob 实现了序列化 / 反序列化一个 golang 结构的能力。

电脑上如果 PDF 不展示或者展示不正常,使用 Chrome 并安装 PDF Viewer 插件。其他情况请下载文件查看:golang-package-gob.pdf

Rob Pike 描述了 gob 的 设计理念

  • 易用:Go 有反射能力,schema 打包在 buffer 中,decoder 不需要知道 struct 的结构即可解包;因此也不需要一个专门的 "protocol compiler"(比如 Protobuf 的 protoc)
  • Go-centric:因为上面的原因, gob 并不打算实现成各语言通用的
  • 性能:gob 使用二进制协议(而不是 JSON / XML 这种文本协议)并尽量使编解码高效

同时描述了 gob 与 Protobuf 理念上的不同:

  • PB 只支持 struct,不能只 encode 一个整数或者一个数组
  • PB 对 field 的 required / optional 设计比较啰嗦(Rob Pike 写这篇文章时只有 PB2,PB3 部分解决了它说的问题),而 gob 并不要求严格的字段对应,一切 field 都是 optional

interface value 的处理

gob.Encoder 可以 encode 一个 interface value,但是你需要提前将其具体类型注册;如果不注册具体类型,而又可能有多个类实现同个 interface,那 gob 在解包时并无法知道要将该 interface value 解为什么具体类型。打包好的二进制会包含类型名。比如:

package main

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

// Point 实现了 Pythagoras interface
type Point struct {
	X, Y int
}

func (p Point) Hypotenuse() float64 {
	return math.Hypot(float64(p.X), float64(p.Y))
}

type Pythagoras interface {
	Hypotenuse() float64
}

func main() {
	var network bytes.Buffer

	// 注册具体的类型。无论是 encode 还是 decode 的代码都需要注册。
	gob.Register(Point{})

	enc := gob.NewEncoder(&network)
	var p1 Pythagoras = Point{3, 4}

	// 这里需要传 &p1 而不是 p1。如果传的是 p1,gob 打包的是具体的 Point 类型,
	// 而不是 Pythagoras interface 类型。
	// 原因见:https://blog.golang.org/laws-of-reflection(未理解)
	if err := enc.Encode(&p1); err != nil {
		log.Fatal("encode:", err)
	}

	dec := gob.NewDecoder(&network)
	var p2 Pythagoras
	if err := dec.Decode(&p2); err != nil {
		log.Fatal("decode:", err)
	}

	// 打印 {3, 4}
	fmt.Println(p2)
}

扩展性

任何类型实现了下面的接口后,可以实现自定义的二进制格式:

  • Encode:GobEncoder or encoding.BinaryMarshaler
  • Decode:GobDecoder or encoding.BinaryUnmarshaler