5

Would it be possible to use Gob encoding for appending structs in series to the same file using append? It works for writing, but when reading with the decoder more than once I run into:

extra data in buffer

So I wonder if that's possible in the first place or whether I should use something like JSON to append JSON documents on a per line basis instead. Because the alternative would be to serialize a slice, but then again reading it as a whole would defeat the purpose of append.

4

3 回答 3

4

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

于 2017-04-04T06:30:02.057 回答
1

除了上述之外,我建议使用中间结构来排除 gob 头:

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "io"
    "log"
)

type Point struct {
    X, Y int
}

func main() {
    buf := new(bytes.Buffer)
    enc, _, err := NewEncoderWithoutHeader(buf, new(Point))
    if err != nil {
        log.Fatal(err)
    }
    enc.Encode(&Point{10, 10})
    fmt.Println(buf.Bytes())
}


type HeaderSkiper struct {
    src io.Reader
    dst io.Writer
}

func (hs *HeaderSkiper) Read(p []byte) (int, error) {
    return hs.src.Read(p)
}

func (hs *HeaderSkiper) Write(p []byte) (int, error) {
    return hs.dst.Write(p)
}

func NewEncoderWithoutHeader(w io.Writer, sample interface{}) (*gob.Encoder, *bytes.Buffer, error) {
    hs := new(HeaderSkiper)
    hdr := new(bytes.Buffer)
    hs.dst = hdr

    enc := gob.NewEncoder(hs)
    // Write sample with header info
    if err := enc.Encode(sample); err != nil {
        return nil, nil, err
    }
    // Change writer
    hs.dst = w
    return enc, hdr, nil
}

func NewDecoderWithoutHeader(r io.Reader, hdr *bytes.Buffer, dummy interface{}) (*gob.Decoder, error) {
    hs := new(HeaderSkiper)
    hs.src = hdr

    dec := gob.NewDecoder(hs)
    if err := dec.Decode(dummy); err != nil {
        return nil, err
    }

    hs.src = r
    return dec, nil
}
于 2020-03-24T08:38:28.453 回答
0

除了出色的 icza 答案外,您还可以使用以下技巧将已写入数据的 gob 文件附加到 gob 文件中:当第一次追加写入并丢弃第一个编码时:

  1. 像往常一样创建文件编码gob(首先编码写入头)
  2. 关闭文件
  3. 打开文件进行追加
  4. 使用和中间编写器编码虚拟结构(写入标头)
  5. 重置写入器
  6. 像往常一样编码 gob(不写入标题)

例子:

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
)

type Record struct {
    ID   int
    Body string
}

func main() {
    r1 := Record{ID: 1, Body: "abc"}
    r2 := Record{ID: 2, Body: "def"}

    // encode r1
    var buf1 bytes.Buffer
    enc := gob.NewEncoder(&buf1)
    err := enc.Encode(r1)
    if err != nil {
        log.Fatal(err)
    }

    // write to file
    err = ioutil.WriteFile("/tmp/log.gob", buf1.Bytes(), 0600)
    if err != nil {
        log.Fatal()
    }

    // encode dummy (which write headers)
    var buf2 bytes.Buffer
    enc = gob.NewEncoder(&buf2)
    err = enc.Encode(Record{})
    if err != nil {
        log.Fatal(err)
    }

    // remove dummy
    buf2.Reset()

    // encode r2
    err = enc.Encode(r2)
    if err != nil {
        log.Fatal(err)
    }

    // open file
    f, err := os.OpenFile("/tmp/log.gob", os.O_WRONLY|os.O_APPEND, 0600)
    if err != nil {
        log.Fatal(err)
    }

    // write r2
    _, err = f.Write(buf2.Bytes())
    if err != nil {
        log.Fatal(err)
    }

    // decode file
    data, err := ioutil.ReadFile("/tmp/log.gob")
    if err != nil {
        log.Fatal(err)
    }

    var r Record
    dec := gob.NewDecoder(bytes.NewReader(data))
    for {
        err = dec.Decode(&r)
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(r)
    }
}
于 2019-12-19T12:24:22.440 回答