首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

encoding/gob

  • import "encoding/gob"
  • 概述
  • 索引
  • 示例

概述

Package gob 管理 gobs 流 - 在编码器(发送器)和解码器(接收器)之间交换的二进制值。一个典型的用途是传输远程过程调用(RPC)的参数和结果,如由“net/rpc”包提供的那些。

该实现为流中的每种数据类型编译自定义编解码器,并且在使用单个编码器传输值流时缓解编译成本,效率最高。

基础

gob 是自我描述的。流中的每个数据项都有一个类型的规范,用一组预定义的类型表示。指针不传输,但它们指向的内容被传送; 也就是说,这些值是平坦的。nil指针是不允许的,因为它们没有价值。递归类型工作正常,但递归值(带周期的数据)是有问题的。这可能会改变。

要使用 gobs,请创建一个编码器,并将其与一系列数据项一起呈现为值或地址,这些值可以取消引用值。编码器确保在需要之前发送所有类型的信息。在接收端,解码器从编码流中检索值并将它们解压缩成局部变量。

类型和值

源和目标值/类型不需要完全对应。对于结构体来说,源中但没有接收变量的域(由名称标识)将被忽略。在接收变量中但从传输类型或值中缺失的字段将在目标中被忽略。如果同时存在两个同名的字段,则其类型必须兼容。接收器和发射器都将执行所有必要的间接和取消引用,以在 gob 和实际 Go 值之间进行转换。例如,示意性的采空区类型,

代码语言:javascript
复制
struct { A, B int }

可以从以下任何一种 Go 类型发送或接收:

代码语言:txt
复制
struct { A, B int }	// 相同
*struct { A, B int }	// 结构的额外间接
struct { *A, **B int }	// 额外的字段间接
struct { A, B int64 }	// 不同的具体值类型;见下文

它也可以被接收到以下任何一个中:

代码语言:txt
复制
struct { A, B int }	// 相同
struct { B, A int }	// 订购无所谓; 匹配是按名称
struct { A, B, C int }	// 额外字段(C)被忽略
struct { B int }	// 丢失的字段(A)被忽略; 数据将被删除
struct { B, C int }	// 丢失的字段(A)被忽略; 额外字段(C)被忽略。

尝试接收这些类型将导致解码错误:

代码语言:txt
复制
struct { A int; B uint }	// 改变B的签字
struct { A int; B float }	// 改变B的类型
struct { }			// 没有共同的字段名称
struct { C, D int }		// 没有共同的字段名称

整数以两种方式传输:任意精度有符号整数或任意精度无符号整数。gob 格式中没有 int8,int16 等格式歧视;只有有符号和无符号的整数。如下所述,发送器以可变长度编码发送值; 接收器接受该值并将其存储在目标变量中。浮点数总是使用 IEEE-754 64 位精度发送(见下文)。

带符号的整数可以接收到任何有符号的整数变量中:int,int16 等;无符号整数可以接收到任何无符号整数变量中; 并且可以将浮点值接收到任何浮点变量中。但是,目标变量必须能够表示值,否则解码操作将失败。

结构,数组和切片也被支持。结构仅对导出的字段进行编码和解码。字符串和字节数组以特殊的高效表示形式提供支持(请参见下文)。当一个片段被解码时,如果现有片段具有容量,片段将被扩展到位; 如果不是,则分配一个新数组。无论如何,结果切片的长度报告解码的元素的数量。

通常,如果需要分配,解码器将分配内存。如果不是,它将使用从流中读取的值更新目标变量。它不会首先初始化它们,因此如果目标是复合值(如地图,结构或切片),则解码值将按照元素方式合并到现有变量中。

func和chan无法使用gob编码,若尝试直接发送func或chan类型的数据将会失败,若是在struct中,字段类型为为func 或 chan,则视为非导出/非公开的字段,在gob编码时,直接忽略

Gob 可以按照优先顺序调用相应的方法来编码实现 GobEncoder 或 encoding.BinaryMarshaler 接口的任何类型的值。

Gob 可以通过调用相应的方法来解码实现 GobDecoder 或编码的任何类型的值 encoding.BinaryUnmarshaler 接口也会按照该优先顺序再次进行解码。

编码细节

本节介绍编码,对大多数用户不重要的详细信息。细节从下到上呈现。

无符号整数以两种方式之一发送。如果它小于128,则将其作为具有该值的字节发送。否则,它将作为保留该值的最小长度的大端(高字节的第一个)字节流发送,前面是一个保存字节计数的字节,取反。因此,0发送为(00),7发送为(07),256发送为 (FE 01 00)。

布尔值在无符号整数内编码:0代表假,1代表真。

一个有符号整数i被编码在一个无符号整数 u 中。在 u 中,位1向上包含该值; 位0表示它们是否应该在收到时补充。编码算法如下所示:

代码语言:txt
复制
var u uint
if i < 0 {
	u = (^uint(i) << 1) | 1 // 补充 i, 位0是1
} else {
	u = (uint(i) << 1) // 不补充 i, 位0是0
}
encodeUnsigned(u)

低位因此类似于符号位,但是使其成为补码位而不是保证最大的负整数不是特例。例如,-129=^128=(^256>>1)编码为(FE 01 01)。

浮点数字总是作为 float64 值的表示形式发送。该值使用 math.Float64bits 转换为 uint64。uint64 然后被字节反转并作为常规无符号整数发送。字节反转意味着尾数的指数和高精度部分先行。由于低位通常为零,因此可以节省编码字节。例如,17.0 只用三个字节编码 (FE 31 40)。

字符串和字节片段作为无符号计数发送,然后是该值的许多未解释的字节。

所有其他切片和数组都作为无符号计数发送,然后递归地使用标准采样编码作为其类型。

地图是作为无符号计数发送的,然后是许多键元素对。空的但非零的地图被发送,所以如果接收方还没有分配一个地图,则一直会在接收时分配一个地图,除非传输的地图是零而不是在顶层。

在切片和阵列以及地图中,即使所有元素均为零,也会传输所有元素,即使是零值元素。

结构以(字段号,字段值)对的序列发送。字段值是使用递归的类型的标准gob编码发送的。如果某个字段的类型为零(数组除外;请参见上文),则该字段在传输中将被忽略。字段编号由编码结构的类型定义:编码类型的第一个字段是字段0,第二个字段是字段1等。编码值时,字段编号为 delta 编码以提高效率,字段始终按照增加字段的顺序发送; 三角洲因此没有签名。增量编码的初始化将字段编号设置为-1,因此具有值7的无符号整数字段0作为无符号的增量= 1,无符号的值= 7或(01 07)发送。最后,在所有字段发送之后,终止标记表示结构的结束。

接口类型不检查兼容性;所有接口类型都作为单个“interface”类型的成员进行处理,类似于 int 或 []byte - 实际上它们都被视为 interface{}。接口值以字符串的形式传输,标识发送的具体类型(必须通过调用寄存器预先定义的名称),然后是以下数据长度的字节数(因此,如果不能存储),然后是存储在接口值中的具体(动态)值的通常编码。(一个零接口值由空字符串标识并且不传送任何值。)一旦收到,解码器就会验证解包后的具体项目是否满足接收变量的接口。

如果一个值传递给 Encode,并且该类型不是一个结构体(或指向结构体的指针等),为了简化处理,它被表示为一个字段的结构。这样做的唯一可见效果是在值之后编码一个零字节,就像编码结构的最后一个字段之后一样,以便解码算法知道顶级值何时完成。

类型的表示如下所述。当在编码器和解码器之间的给定连接上定义类型时,它被分配一个有符号的整数类型ID。当调用Encoder.Encode(v)时,它确保为v及其所有元素的类型分配一个id,然后发送该对 (typeid, encoded-v),其中 typeid 是编码类型的类型 id v 和 encoded-v 是值v的 gob 编码。

为了定义一个类型,编码器选择一个未使用的肯定类型id,并发送这个对 (-type id, encoded-type),其中 encoded-type 是 wireType 描述的 gob 编码,由以下类型构造而成:

代码语言:txt
复制
type wireType struct {
	ArrayT  *ArrayType
	SliceT  *SliceType
	StructT *StructType
	MapT    *MapType
}
type arrayType struct {
	CommonType
	Elem typeId
	Len  int
}
type CommonType struct {
	Name string // 结构类型的名称
	Id  int    // 类型的id,重复,所以它在类型内
}
type sliceType struct {
	CommonType
	Elem typeId
}
type structType struct {
	CommonType
	Field []*fieldType // 结构的字段。
}
type fieldType struct {
	Name string // 该字段的名称。
	Id   int    // 字段的类型ID,必须已定义
}
type mapType struct {
	CommonType
	Key  typeId
	Elem typeId
}

如果存在嵌套类型标识,则必须在使用顶级类型标识描述 encoded-v 之前定义所有内部类型标识的类型。

为了简化设置,连接被定义为先验地理解这些类型,以及基本采样类型 int,uint 等。它们的 ID 是:

代码语言:txt
复制
bool        1
int         2
uint        3
float       4
[]byte      5
string      6
complex     7
interface   8
// 保留ID的差距。
WireType    16
ArrayType   17
CommonType  18
SliceType   19
StructType  20
FieldType   21
// 22是fieldType的切片。
MapType     23

最后,通过对 Encode 的调用创建的每条消息前面都有一个编码的无符号整数数量,该数量是消息中剩余的字节数。在初始类型名称之后,接口值以相同方式包装; 实际上,接口值的作用类似于 Encode 的递归调用。

总之,一个 gob 流看起来像

代码语言:javascript
复制
(byteCount (-type id, encoding of a wireType)* (type id, encoding of a value))*

其中*表示重复次数为零或更多,并且值的类型 ID 必须预先定义或在流中的值之前定义。

兼容性:对软件包的未来更改将尽力保持与使用先前版本编码的流的兼容性。也就是说,这个软件包的任何发布版本都应该能够解码使用任何以前发布的版本编写的数据,并受到诸如安全修复等问题的影响。有关背景信息,请参阅Go兼容性文档:https//golang.org/doc/go1compat

关于 gob 线格式的设计讨论,请参阅“Gobs of data(数据采集)”:https//blog.golang.org/gobs-of-data

示例(Basic)

这个例子显示了软件包的基本用法:创建一个编码器,传输一些值,用解码器接收它们。

代码语言:txt
复制
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() {
	// 初始化编码器和解码器。 通常是enc和dec
	// 绑定到网络连接和编码器和解码器会
	// 在不同的进程中运行。
	var network bytes.Buffer        // 替代网络连接
	enc := gob.NewEncoder(&network) // 将写入网络。
	dec := gob.NewDecoder(&network) // 将从网络上读取。
	// Encoding(发送)一些值。
	err := enc.Encode(P{3, 4, 5, "Pythagoras"})
	if err != nil {
		log.Fatal("encode error:", err)
	}
	err = enc.Encode(P{1782, 1841, 1922, "Treehouse"})
	if err != nil {
		log.Fatal("encode error:", err)
	}

	// Decode(接收)并打印值。
	var q Q
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decode error 1:", err)
	}
	fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decode error 2:", err)
	}
	fmt.Printf("%q: {%d, %d}\n", q.Name, *q.X, *q.Y)

}

示例(EncodeDecode)

本示例传输一个实现自定义编码和解码方法的值。

代码语言:txt
复制
package main

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

// Vector类型具有未导出的字段,包无法访问。
// 因此,我们编写了一个BinaryMarshal/BinaryUnmarshal方法对来允许我们
// 使用gob包发送和接收类型。 这些接口是
// 在“encoding”包中定义。
// 我们可以等效地使用本地定义的GobEncode/GobDecoder
// 接口。
type Vector struct {
	x, y, z int
}

func (v Vector) MarshalBinary() ([]byte, error) {
	// 一个简单的编码:纯文本。
	var b bytes.Buffer
	fmt.Fprintln(&b, v.x, v.y, v.z)
	return b.Bytes(), nil
}

// UnmarshalBinary修改接收器,因此必须使用指针接收器。
func (v *Vector) UnmarshalBinary(data []byte) error {
	// 一个简单的编码:纯文本。
	b := bytes.NewBuffer(data)
	_, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)
	return err
}

// 此示例传输实现自定义编码和解码方法的值。
func main() {
	var network bytes.Buffer // 替代(Stand-in)网络。

	// 创建编码器并发送值。
	enc := gob.NewEncoder(&network)
	err := enc.Encode(Vector{3, 4, 5})
	if err != nil {
		log.Fatal("encode:", err)
	}

	// 创建解码器并接收值。
	dec := gob.NewDecoder(&network)
	var v Vector
	err = dec.Decode(&v)
	if err != nil {
		log.Fatal("decode:", err)
	}
	fmt.Println(v)

}

示例(Interface)

此示例显示如何对接口值进行编码。与常规类型的主要区别在于注册实现接口的具体类型。

代码语言:txt
复制
package main

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

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 // 替代(Stand-in)网络。

	// 我们必须注册编码器和解码器的具体类型(这将是
	// 通常在与编码器不同的机器上)。 在每一端,这告诉了
	// 发送具体类型的引擎实现接口。
	gob.Register(Point{})

	// 创建编码器并发送一些值。
	enc := gob.NewEncoder(&network)
	for i := 1; i <= 3; i++ {
		interfaceEncode(enc, Point{3 * i, 4 * i})
	}

	// 创建解码器并接收一些值。
	dec := gob.NewDecoder(&network)
	for i := 1; i <= 3; i++ {
		result := interfaceDecode(dec)
		fmt.Println(result.Hypotenuse())
	}

}

// interfaceEncode将接口值编码到编码器中。
func interfaceEncode(enc *gob.Encoder, p Pythagoras) {
	// 除非具体类型,否则编码将失败
	// 注册。 我们在调用函数中注册了。
	// 将指针传递给接口,以便Encode看到(并因此发送)一个值
	// 界面类型。 如果我们直接传递p,它会看到具体的类型。
	// 有关背景,请参阅博客文章“(The Laws of Reflection)反思的法则”。
	err := enc.Encode(&p)
	if err != nil {
		log.Fatal("encode:", err)
	}
}

// interfaceDecode解码流中的下一个接口值并返回。
func interfaceDecode(dec *gob.Decoder) Pythagoras {
	// 除非线路上的具体类型已经解码,否则解码将失败
	// 注册。 我们在调用函数中注册了。
	var p Pythagoras
	err := dec.Decode(&p)
	if err != nil {
		log.Fatal("decode:", err)
	}
	return p
}

索引

  • func Register(value interface{})
  • func RegisterName(name string, value interface{})
  • type CommonType
  • type Decoder
  • func NewDecoder(r io.Reader) *Decoder
  • func (dec *Decoder) Decode(e interface{}) error
  • func (dec *Decoder) DecodeValue(v reflect.Value) error
  • type Encoder
  • func NewEncoder(w io.Writer) *Encoder
  • func (enc *Encoder) Encode(e interface{}) error
  • func (enc *Encoder) EncodeValue(value reflect.Value) error
  • type GobDecoder
  • type GobEncoder

示例

Package (Basic) Package (EncodeDecode) Package (Interface)

包文件

代码语言:javascript
复制
func Register(value interface{})

在其内部类型名称下注册记录类型,由该类型的值标识。该名称将标识作为接口变量发送或接收的值的具体类型。只需要注册将作为接口值实现传输的类型。期望仅在初始化期间使用,如果类型和名称之间的映射不是双向映射,则会发生混乱。

func RegisterName(查看源代码)

代码语言:javascript
复制
func RegisterName(name string, value interface{})

RegisterName 与 Register 类似,但使用提供的名称而不是类型的默认值。

type CommonType(查看源代码)

CommonType 保存所有类型的元素。它是一个历史工件,保存为二进制兼容性,并且仅为了包类型描述符的编码而导出。它不适合客户直接使用。

代码语言:javascript
复制
type CommonType struct {
        Name string
        Id   typeId
}

解码器管理从连接的远程端读取的类型和数据信息的接收。

解码器只对解码输入大小进行基本的理智检查,并且其限制是不可配置的。解码来自不受信任来源的 gob 数据时请小心。

代码语言:txt
复制
type Decoder struct {
        // 包含已过滤或未导出的字段
}

func NewDecoder(查看源代码)

代码语言:javascript
复制
func NewDecoder(r io.Reader) *Decoder

NewDecoder 返回一个从 io.Reader 读取的新解码器。如果r不实现 io.ByteReader,它将被包装在一个 bufio.Reader 中。

func (*Decoder) Decode(查看源代码)

代码语言:javascript
复制
func (dec *Decoder) Decode(e interface{}) error

解码从输入流中读取下一个值并将其存储在由空接口值表示的数据中。如果e为零,则该值将被丢弃。否则,e 下面的值必须是指向下一个接收数据项的正确类型的指针。如果输入是在 EOF,解码返回 io.EOF 并且不修改e。

func (*Decoder) DecodeValue(查看源代码)

代码语言:javascript
复制
func (dec *Decoder) DecodeValue(v reflect.Value) error

DecodeValue 从输入流中读取下一个值。如果v是 zero reflect.Value(v.Kind()==Invalid),则 DecodeValue 丢弃该值。否则,它将值存储到v中。在这种情况下,v必须表示一个非零指向数据的指针,或者是可赋值的reflect.Value(v.CanSet())如果输入位于 EOF, DecodeValue 返回 io.EOF,不修改v。

编码器管理类型和数据信息传输到连接的另一端。

代码语言:txt
复制
type Encoder struct {
        // 包含已过滤或未导出的字段
}

func NewEncoder(查看源代码)

代码语言:javascript
复制
func NewEncoder(w io.Writer) *Encoder

NewEncoder 返回一个将在 io.Writer 上传输的新编码器。

func (*Encoder) Encode(查看源代码)

代码语言:javascript
复制
func (enc *Encoder) Encode(e interface{}) error

编码传输由空接口值表示的数据项,保证所有必需的类型信息先传送。传递一个零指针给编码器会惊慌,因为它们不能通过 gob 传输。

func (*Encoder) EncodeValue(查看源代码)

代码语言:javascript
复制
func (enc *Encoder) EncodeValue(value reflect.Value) error

EncodeValue 传输由反射值表示的数据项,保证所有必要的类型信息都先传输完毕。将一个零指针传递给 EncodeValue 将会发生混乱,因为它们不能通过 gob 进行传输。

type GobDecoder(查看源代码)

GobDecoder 是描述数据的接口,它提供自己的例程来解码由 GobEncoder 发送的传输值。

代码语言:txt
复制
type GobDecoder interface {
        // GobDecode会覆盖接收器,接收器必须是指针,
        // 使用写入的字节切片表示的值
        // 通过GobEncode,通常用于相同的具体类型。
        GobDecode([]byte) error
}

type GobEncoder(查看源代码)

GobEncoder 是描述数据的接口,它为编码值提供了自己的表示,以便传输给 GobDecoder。实现 GobEncoder 和 GobDecoder 的类型可以完全控制其数据的表示,因此可能包含私有字段,通道和函数,这些通常不会在 gob 流中传输。

注意:由于 gobs 可以永久保存,因此确保 GobEncoder 使用的编码在软件变化时保持稳定是一个很好的设计。例如,GobEncode 在编码中包含一个版本号可能是有意义的。

代码语言:txt
复制
type GobEncoder interface {
        // GobEncode返回表示编码的字节切片
        // 用于传输到GobDecoder的接收器,通常是相同的
        // 具体类型。
        GobEncode() ([]byte, error)
}

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com