通过多次附加来检索写入文件的采空区

Retrieving gobs written to file by appending several times

我正在尝试使用 encoding/gob 将数据存储到文件并稍后加载。我希望能够将新数据附加到文件并稍后加载所有保存的数据,例如重新启动我的应用程序后。使用 Encode() 存储到文件时没有问题,但在读取时似乎总是只得到最先存储的项目,而不是简洁存储的项目。

这是一个最小的例子:https://play.golang.org/p/patGkKDLhM

如您所见,它可以向编码器写入两次然后再读回。但是当关闭文件并以附加模式再次打开它时,写入似乎有效,但读取仅适用于前两个元素(之前已写入)。无法检索新添加的两个结构,我得到错误:

panic: extra data in buffer

我知道 and I also read https://groups.google.com/forum/#!topic/golang-nuts/bn6vjC5Abd8

最后,我还发现了https://gist.github.com/kjk/8015952,这似乎表明我正在尝试做的事情是行不通的。为什么?这个错误是什么意思?

我还没有使用 encoding/gob 包(看起来很酷,我可能需要为它找一个项目)。但是阅读 godoc 时,在我看来,每个编码都是一个预期从头到尾解码的记录。也就是说,一旦你 Encode 一个流,生成的字节是一个完整的集合,从头到尾尊重整个流 - 以后不能通过再次编码附加到后面。

godoc 指出编码的 gob 是自描述的。在编码流的开头,它描述了整个数据集结构、类型等,包括字段名称。然后字节流中的内容是那些导出字段的值的大小和字节表示。

然后可以假设文档中省略的内容是因为流在一开始就自我描述,包括将要传递的每个字段,即就是 Decoder 所关心的。 Decoder 将不知道在描述的内容之后添加的任何连续字节,因为它只看到开头描述的内容。因此,该错误消息 panic: extra data in buffer 是准确的。

在您的 Playground 示例中,您对同一个编码器实例进行了两次编码,然后关闭了文件。由于您正好传入两条记录,并对两条记录进行编码,因此 可能会起作用 因为编码器的单个实例可能会将两个 Encode 调用视为单个编码流。然后,当您关闭文件 io 的流时,gob 现在已完成 - 并且该流被视为单个记录(即使您发送了两种类型)。

在解码函数中也是如此,您从同一个流中读取了 X 次。但是,您在关闭文件时写了一条记录——一条记录中实际上有两种类型。因此,为什么它在读取 2 时有效,并且正好是 2。但如果读取超过 2,则会失败。

如果您想将其存储在单个文件中,解决方案是您需要为每个完整 "write" 或编码器 instance/session 创建自己的索引。有些形成您自己的 Block 方法,允许您使用 "begin" 和 "end" 标记包装或定义写入磁盘的每个条目。这样,当回读文件时,由于 begin/end 标记,您确切地知道要分配哪个缓冲区。一旦缓冲区中有一条记录,就可以使用 gob 的 Decoder 对其进行解码。并在每次写入后关闭文件。

我用于此类标记的模式类似于:

uint64:uint64
uint64:uint64
...

第一个是起始字节数,第二个以冒号分隔的条目是它的长度。不过,我通常将其存储在另一个文件中,适当地调用 indexes。这样它可以快速读入内存,然后我可以流式传输大文件,确切地知道每个开始和结束地址在字节流中的位置。

另一种选择是将每个 gob 存储在自己的文件中,使用文件系统目录结构按照您认为合适的方式进行组织(或者甚至可以使用目录来定义类型,例如)。那么每个文件的存在都是一条记录。这就是我使用事件溯源技术呈现的 json 的方式,将数百万个文件组织在目录中。

综上所述,在我看来,一份gob的数据就是从头到尾的完整数据集——一个"record"就有你了。如果要存储多个 encodings/multiple gob,则需要创建自己的索引以在存储它们时跟踪每个 gob 字节的开始和 size/end。然后,您将要 Decode 分别输入每个条目。