为了账号安全,请及时绑定邮箱和手机立即绑定

编码/gob 是确定性的吗?

编码/gob 是确定性的吗?

Go
陪伴而非守候 2021-11-15 20:33:02
我们是否可以期望两个 Go 对象 x, y 使得 x 等于 y(假设没有接口和映射的技巧,只有结构和数组)gob_encode(x) 和 gob_encode(y) 的输出将始终相同?edit (Jun 8 2018):当涉及地图时,gob 编码是不确定的。这是由于地图的随机迭代顺序,导致它们的序列化是随机排序的。
查看完整描述

3 回答

?
红颜莎娜

TA贡献1842条经验 获得超12个赞

只要它“完成工作”,您就不应该真正在意。但是当前的encoding/gob实现是确定性的。但是(继续阅读)!


自从:


一连串的gobs是不言自明的。流中的每个数据项前面都有其类型的规范,以一小组预定义类型表示。


这意味着如果您第一次对某个类型的值进行编码,则会发送类型信息。如果您对相同类型的另一个值进行编码,则不会再次传输类型描述,只是对其先前规范的引用。因此,即使您对相同的值进行两次编码,它也会产生不同的字节序列,因为第一个将包含类型规范和值,第二个将仅包含一个类型 ref(例如类型 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.Encoders 并以相同的顺序写入相同的值,它们将产生精确的结果。


请注意,在前面的语句中“相同顺序”也很重要。因为类型说明是在发送这种类型的第一个值时传输的,所以不同类型的值以不同的顺序发送也会以不同的顺序传输类型说明,因此类型的引用/标识符可能不同,这意味着当一个值此类类型已编码,将使用/发送不同类型的引用/ID。


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


查看完整回答
反对 回复 2021-11-15
?
慕哥9229398

TA贡献1877条经验 获得超6个赞

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

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


查看完整回答
反对 回复 2021-11-15
?
米琪卡哇伊

TA贡献1998条经验 获得超6个赞

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


例子:


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会产生不同的字节和不同的哈希码。


尽管使用了不同的对象/指针,但还是会发生这种情况。


查看完整回答
反对 回复 2021-11-15
  • 3 回答
  • 0 关注
  • 206 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信