【问题标题】:Retrieving gobs written to file by appending several times通过多次附加来检索写入文件的 gob
【发布时间】:2016-04-03 23:33:20
【问题描述】:

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

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

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

恐慌:缓冲区中有额外数据

我知道Append to golang gob in a file on disk,我也读过https://groups.google.com/forum/#!topic/golang-nuts/bn6vjC5Abd8

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

【问题讨论】:

    标签: file go append gob


    【解决方案1】:

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

    godoc 声明编码的gob 是自我描述的。在编码流的开头,它描述了将跟随的整个数据集结构、类型等,包括字段名称。那么字节流后面就是那些Exported字段的值的大小和字节表示。

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

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

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

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

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

    uint64:uint64
    uint64:uint64
    ...
    

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

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

    总而言之,在我看来,gob 的数据是从头到尾的完整数据集 - 一个“记录”有你。如果您想存储多个编码/多个 gob,则需要创建自己的索引来跟踪每个 gob 字节的开始和大小/结束,因为您存储它们。然后,您将需要分别Decode 每个条目。

    【讨论】:

    • 谢谢,这是有道理的。另一种选择应该是在连续写入之间不关闭编码器。这应该在应用关闭之前有效。
    • 是的,不关闭也行。 Gob 的特殊之处在于,对于它遇到的每个新类型,它都会编写一个描述记录来定义字段和类型。通过创建新的编码器,它会忘记定义了哪些类型,因此它会在第二次写入时再次开始定义它们。我认为这会让解码器感到困惑。
    • 这几乎就像他们缺少一个 Close 方法来表明它已经完成了。
    • 有人可能会说 Close 是隐含的:当不再引用编码器时。
    猜你喜欢
    • 2017-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    相关资源
    最近更新 更多