encoding/gob 是确定性的吗?

Is encoding/gob deterministic?

我们能否期望两个 Go 对象 x, y 使得 x 等于 y(假设接口和映射没有技巧,只是结构和数组)gob_encode(x) 和 gob_encode(y) 总是一样的吗?

编辑(2018 年 6 月 8 日):

当涉及 maps 时,

gob 编码是 non-deterministic。这是由于地图的随机迭代顺序,导致它们的序列化顺序随机。

只要它 "gets the job done" 你不应该真的在意。但是当前的 encoding/gob 实现是确定性的。但是(继续阅读)!

开始于:

A stream of gobs is self-describing. Each data item in the stream is preceded by a specification of its type, expressed in terms of a small set of predefined types.

这意味着如果您第一次对某个类型的值进行编码,则会发送类型信息。如果您对同一类型的另一个值进行编码,则不会再次传输类型描述,只是对其先前规范的引用。因此,即使您对相同的值进行两次编码,它也会产生不同的字节序列,因为第一个将包含类型规范和值,第二个将仅包含一个类型引用(例如类型 id)和值。

看这个例子:

type Int struct{ X int }

b := &bytes.Buffer{}
e := gob.NewEncoder(b)

e.Encode(Int{1})
fmt.Println(b.Bytes())

e.Encode(Int{1})
fmt.Println(b.Bytes())

e.Encode(Int{1})
fmt.Println(b.Bytes())

输出(在 Go Playground 上尝试):

[23 255 129 3 1 1 3 73 110 116 1 255 130 0 1 1 1 1 88 1 4 0 0 0 5 255 130 1 2 0]
[23 255 129 3 1 1 3 73 110 116 1 255 130 0 1 1 1 1 88 1 4 0 0 0 5 255 130 1 2 0 5 255 130 1 2 0]
[23 255 129 3 1 1 3 73 110 116 1 255 130 0 1 1 1 1 88 1 4 0 0 0 5 255 130 1 2 0 5 255 130 1 2 0 5 255 130 1 2 0]

正如所见,第一个 Encode() 生成大量字节加上我们的 Int 值为 [5 255 130 1 2 0] 的值,第二个和第三个调用添加相同的 [5 255 130 1 2 0] 序列.

但是,如果您创建 2 个不同的 gob.Encoder 并以相同的顺序写入相同的值,它们将产生准确的结果。

注意前面的语句"same order"也很重要。因为在发送这种类型的第一个值时传输类型规范,以不同顺序发送不同类型的值也会以不同顺序传输类型规范,因此类型的 references/identifiers 可能不同,这意味着当一个这种类型的值被编码,不同类型 reference/id 将是 used/sent.

另请注意,gob 包的实现可能会因版本而异。这些更改将向后兼容(它们必须明确说明是否出于某种原因它们会进行向后不兼容的更改),但向后兼容并不意味着输出是相同的。所以不同的 Go 版本可能会产生不同的结果(但所有兼容版本都可以解码)。

可能应该注意接受的答案是不正确的:encoding/gob 没有以确定的方式对地图元素进行排序:https://play.golang.org/p/Hh3_5Kb3Znn

我已经分叉 encoding/gob 并添加了一些代码,以便在将地图写入流之前按键排序地图。这会影响性能,但我的特定应用程序不需要高性能。记住自定义编组器可以打破这个,所以小心使用:https://github.com/dave/stablegob

如果您使用不同的类型和不同的编码器,它也不是确定性的。

示例:

package main

import (
    "bytes"
    "crypto/sha1"
    "encoding/gob"
    "encoding/hex"
    "log"
)

func main() {
    encint()
    encint64()
    encstring()

}

func encint() {
    s1 := []int{0, 2, 4, 5, 7}
    buf2 := bytes.Buffer{}
    enc2 := gob.NewEncoder(&buf2)
    enc2.Encode(s1)
}

func encint64() {
    s1 := []int64{0, 2, 4, 5, 7}
    buf2 := bytes.Buffer{}
    enc2 := gob.NewEncoder(&buf2)
    enc2.Encode(s1)
}

func encstring() {
    s1 := []string{"a", "b", "c", "d"}
    buf2 := bytes.Buffer{}
    enc2 := gob.NewEncoder(&buf2)
    enc2.Encode(s1)
    log.Println(buf2.Bytes())

    hash := sha1.New()
    hash.Write(buf2.Bytes())
    ret := hash.Sum(nil)
    log.Println(hex.EncodeToString(ret))
}

运行 在 Go Playground

请注意,如果您注释掉 encint()encint64()encstring 将产生不同的字节和不同的哈希码。

尽管使用了不同的 objects/pointers。