我正在尝试使用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似乎表明我想要做的事情不起作用。为什么?这个错误意味着什么?
答案 0 :(得分:2)
我还没有使用encoding/gob
包(看起来很酷,我可能要为它找到一个项目)。但是阅读godoc,在我看来,每个编码都是一个预期从头到尾解码的记录。也就是说,一旦你Encode
一个流,结果字节就是一个完整的集合,从开始到结束都是关于整个流的 - 不能再通过再次编码附加到后面。
godoc声称编码的gob
是自描述的。在编码流的开头,它描述了将遵循的整个数据集结构,类型等,包括字段名称。然后,字节流中的后续内容是那些“导出”字段的值的大小和字节表示。
然后可以假设从文档中省略的是因为流在一开始就自我描述了自己,包括即将传递的每个字段,这就是{ {1}}会关心。 Decoder
将不知道在描述之后添加的任何连续字节,因为它只看到开头描述的内容。因此,该错误消息Decoder
是准确的。
在Playground示例中,您将对同一编码器实例进行两次编码,然后关闭该文件。由于您正在传递两个记录并对两个记录进行编码,因此可能正常工作,因为编码器的单个实例可能会将两个panic: extra data in buffer
调用视为单个编码流。然后,当您关闭文件io的流时,Encode
现在已完成 - 并且该流被视为单个记录(即使您发送了两种类型)。
在解码功能中,您正在从同一个流中读取X次。但是,在关闭文件时,您正在编写单个记录 - 实际上在该单个记录中有两种类型。因此,为什么它在阅读2和完全2时有效。但如果读数超过2则失败。
如果要将其存储在单个文件中,解决方案是您需要为每个完整的“写入”或编码器实例/会话创建自己的索引。有些形成了您自己的Block方法,允许您使用“begin”和“end”标记来包装或定义写入磁盘的每个条目。这样,当读回文件时,由于开始/结束标记,您确切地知道要分配哪个缓冲区。在缓冲区中有一条记录后,就可以使用gob的gob
对其进行解码。并在每次写入后关闭文件。
我用于此类标记的模式类似于:
Decoder
第一个是起始字节数,第二个条目用冒号分隔,是它的长度。我通常将它存储在另一个文件中,称为uint64:uint64
uint64:uint64
...
。这样它就可以快速读入内存,然后我就可以精确地传输大文件了解每个起始和结束地址在字节流中的位置。
另一种选择是将每个indexes
存储在自己的文件中,使用文件系统目录结构进行组织(例如,甚至可以使用目录来定义类型)。那么每个文件的存在就是一条记录。这就是我如何使用我从事件采购技术中渲染的json,存储在目录中组织的数百万个文件。
总而言之,在我看来,gob
数据是从头到尾的完整数据集 - 只有一个“记录”。如果要存储多个编码/多个gob,那么需要创建自己的索引来跟踪存储它们时每个gob
字节的开头和大小/结尾。然后,您需要单独gob
每个条目。