该gob
软件包并非旨在以这种方式使用。gob 流必须由 single 写入gob.Encoder
,也必须由 single 读取gob.Decoder
。
这样做的原因是因为gob
包不仅序列化你传递给它的值,它还传输数据来描述它们的类型:
一连串的gobs是自我描述的。流中的每个数据项之前都有其类型的规范,用一小组预定义类型表示。
这是编码器/解码器的状态——关于什么类型以及它们是如何被传输的——后续的新编码器/解码器将不会(不能)分析“先前的”流以重建相同的状态并继续前一个编码器/解码器关闭。
当然,如果您创建一个gob.Encoder
,您可以使用它来序列化任意数量的值。
您也可以创建一个gob.Encoder
并写入文件,然后再创建一个新gob.Encoder
的,并附加到同一个文件,但您必须使用 2gob.Decoder
来读取这些值,与编码过程完全匹配。
作为演示,我们来看一个例子。此示例将写入内存缓冲区 ( bytes.Buffer
)。2 个后续编码器将写入它,然后我们将使用 2 个后续解码器读取值。我们将写入这个结构的值:
type Point struct {
X, Y int
}
简而言之,紧凑的代码,我使用这个“错误处理程序”函数:
func he(err error) {
if err != nil {
panic(err)
}
}
现在代码:
const n, m = 3, 2
buf := &bytes.Buffer{}
e := gob.NewEncoder(buf)
for i := 0; i < n; i++ {
he(e.Encode(&Point{X: i, Y: i * 2}))
}
e = gob.NewEncoder(buf)
for i := 0; i < m; i++ {
he(e.Encode(&Point{X: i, Y: 10 + i}))
}
d := gob.NewDecoder(buf)
for i := 0; i < n; i++ {
var p *Point
he(d.Decode(&p))
fmt.Println(p)
}
d = gob.NewDecoder(buf)
for i := 0; i < m; i++ {
var p *Point
he(d.Decode(&p))
fmt.Println(p)
}
输出(在Go Playground上试试):
&{0 0}
&{1 2}
&{2 4}
&{0 10}
&{1 11}
请注意,如果我们只使用 1 个解码器来读取所有值(循环直到i < n + m
,当迭代到达时,我们会收到您在问题中发布的相同错误消息n + 1
,因为后续数据不是序列化的Point
,而是一个新的gob
流。
所以如果你想坚持使用这个gob
包来做你想做的事,你必须稍微修改一下,增强你的编码/解码过程。当使用新的编码器时,您必须以某种方式标记边界(因此在解码时,您会知道必须创建一个新的解码器来读取后续值)。
您可以使用不同的技术来实现这一点:
- 您可以在继续写入值之前写出一个数字,一个计数,这个数字将告诉您使用当前编码器写入了多少个值。
- 如果您不想或无法知道当前编码器将写入多少个值,您可以选择在不使用当前编码器写入更多值时写出一个特殊的编码器结束值。在解码时,如果你遇到这个特殊的 end-of-encoder值,你就会知道你必须创建一个新的解码器才能读取更多的值。
这里需要注意的一些事项:
- 如果只使用单个编码器,该
gob
包最高效、最紧凑,因为每次创建和使用新的编码器时,都必须重新传输类型规范,造成更多开销,并使编码/解码过程变慢.
- 您不能在数据流中查找,如果您从头开始读取整个文件直到您想要的值,您只能解码任何值。请注意,即使您使用其他格式(例如 JSON 或 XML),这也有些适用。
如果你想寻找功能,你需要单独管理一个索引文件,它会告诉你新的编码器/解码器从哪个位置开始,所以你可以寻找到那个位置,创建一个新的解码器,然后从那里开始读取值。
检查一个相关问题:Efficient Go serialization of struct to disk