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。
我们能否期望两个 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。