Serialization 使用Gob以追加样式将日志写入文件

Serialization 使用Gob以追加样式将日志写入文件,serialization,go,encoding,binary,gob,Serialization,Go,Encoding,Binary,Gob,是否可以使用Gob编码,使用append将串联结构追加到同一文件中?它适用于书写,但当使用解码器多次阅读时,我会遇到: extra data in buffer 因此,我想知道这是否可能,或者我是否应该使用类似JSON的东西来在每行的基础上附加JSON文档。因为另一种方法是序列化一个切片,但是再次将其作为一个整体读取会破坏追加的目的。包不是设计用于这种方式的。采空区流必须由单个用户写入,也必须由单个用户读取 这是因为gob包不仅序列化了传递给它的值,还传输数据来描述它们的类型: 溪流是自我描述

是否可以使用Gob编码,使用append将串联结构追加到同一文件中?它适用于书写,但当使用解码器多次阅读时,我会遇到:

extra data in buffer
因此,我想知道这是否可能,或者我是否应该使用类似JSON的东西来在每行的基础上附加JSON文档。因为另一种方法是序列化一个切片,但是再次将其作为一个整体读取会破坏追加的目的。

包不是设计用于这种方式的。采空区流必须由单个用户写入,也必须由单个用户读取

这是因为
gob
包不仅序列化了传递给它的值,还传输数据来描述它们的类型:

溪流是自我描述的。流中的每个数据项前面都有其类型的规范,以一小组预定义类型表示

这是编码器/解码器的一种状态–关于它们的类型和传输方式–后续的新编码器/解码器将不会(无法)分析“正在进行的”流以重构相同的状态,并在之前的编码器/解码器停止时继续

当然,如果您创建一个
gob.Encoder
,您可以使用它来序列化任意数量的值

您还可以创建一个
gob.Encoder
并写入一个文件,然后创建一个新的
gob.Encoder
,并附加到同一个文件,但是您必须使用2个
gob.Decoder
读取这些值,与编码过程完全匹配

作为示范,我们来举个例子。此示例将写入内存中的缓冲区()。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)
}
请注意,如果我们只使用1个解码器读取所有值(循环直到
i
),那么当迭代到达
n+1
时,我们会收到您在问题中发布的相同错误消息,因为后续数据不是序列化的
,而是新
gob
流的开始

因此,如果你想坚持使用
gob
包来做你想做的事情,你必须稍微修改、增强你的编码/解码过程。在使用新编码器时,你必须以某种方式标记边界(这样在解码时,你就知道你必须创建一个新的解码器来读取后续值)

您可以使用不同的技术来实现这一点:

  • 在继续写入值之前,您可以写出一个数字、一个计数,这个数字将告诉您使用当前编码器写入了多少值
  • 如果您不想或无法判断当前编码器将写入多少个值,当您不使用当前编码器写入更多值时,您可以选择写入一个特殊的编码器结束值。解码时,如果遇到此特殊的编码器结束值,您将知道必须创建一个新的解码器才能读取更多的v价值观
这里需要注意的是:

  • 如果只使用一个编码器,
    gob
    包是最有效、最紧凑的,因为每次创建和使用新编码器时,都必须重新传输类型规范,从而导致更多开销,并使编码/解码过程变慢
  • 您不能在数据流中查找,只有从开始读取整个文件直到所需的值,才能对任何值进行解码。请注意,即使您使用其他格式(如JSON或XML),这在某种程度上也适用
如果您想要搜索功能,您需要单独管理一个索引文件,它将告诉您新编码器/解码器在哪个位置启动,因此您可以搜索到那个位置,创建一个新解码器,并从那个位置开始读取值


检查一个相关的问题:

此外,为了获得好的icza答案,您可以使用以下技巧将已写入的数据追加到gob文件中:追加第一次写入并放弃第一次编码时:

  • 像往常一样创建文件Encode gob(首先对写入头进行编码)
  • 关闭文件
  • 打开要追加的文件
  • 使用和中间编写器编码虚拟结构(写入头)
  • 重置写入程序
  • 按常规编码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)
        }
    }
    

    除上述内容外,我建议使用中间结构排除采空区总管:

    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
    }
    

    如果您绝对需要使用
    gob
    我建议您使用boltdb。为您的记录器编写一个钩子,并将它们登录到boltdb。此外,移动boltdb文件很容易,因为它只是一个文件-您还可以根据大小、时间或级别创建新的boltdb文件。如果您绝对需要普通文件,我建议使用lumberjack并为其编写钩子您的记录器需要将条目转换为base64。无论如何,使用gob会使分析和监视变得困难,因此InfluxDB或Prometheus也是有效的选项。我在回答中使用了单独的结构略微减少了您的选项。如果数据是同质的,那么您可以使用我的方法,通过一个平庸的结构
    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
    }