Use Gob to write logs to a file in an append style
是否可以使用 Gob 编码将结构串联附加到使用 append 的同一文件中?它适用于写作,但在使用解码器多次阅读时,我遇到了:
1 | extra data in buffer |
所以我首先想知道这是否可行,或者我是否应该使用 JSON 之类的东西来逐行附加 JSON 文档。因为另一种方法是序列化一个切片,但再次将其作为一个整体读取将违背 append.
的目的
读取
这样做的原因是因为
A stream of gobs is self-describing. Each data item in the stream is preceded by a specification of its type, expressed in terms of a small set of predefined types.
这是编码器/解码器的状态——关于它们是什么类型以及它们是如何传输的——,后续的新编码器/解码器将不会(无法)分析"前面的"流以重建相同的状态并继续上一个编码器/解码器停止的地方。
当然,如果您创建单个
你也可以创建一个
作为演示,让我们看一个例子。此示例将写入内存缓冲区 (
1 2 3 | type Point struct { X, Y int } |
简而言之,紧凑的代码,我使用这个"错误处理程序"函数:
1 2 3 4 5 | func he(err error) { if err != nil { panic(err) } } |
现在是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 上试试):
1 2 3 4 5 | &{0 0} &{1 2} &{2 4} &{0 10} &{1 11} |
请注意,如果我们只使用 1 个解码器来读取所有值(循环到
所以如果你想坚持使用
您可以使用不同的技术来实现这一点:
- 在继续写入值之前,您可以写出一个数字、一个计数,这个数字将说明使用当前编码器写入了多少个值。
- 如果您不想或无法确定将使用当前编码器写入多少个值,当您不使用当前编码器。解码时,如果遇到这种特殊的编码器结束值,您就会知道必须创建一个新的解码器才能读取更多值。
这里有一些注意事项:
-
如果只使用单个编码器,
gob 包是最高效、最紧凑的,因为每次创建和使用新编码器时,都必须重新传输类型规范,造成更多开销,并使编码 /解码过程较慢。 - 您不能在数据流中查找,如果您从头开始读取整个文件直到您想要的值,您只能解码任何值。请注意,即使您使用其他格式(例如 JSON 或 XML),这在某种程度上也适用。
如果你想要寻找功能,你需要单独管理一个索引文件,它会告诉你新的编码器/解码器从哪个位置开始,所以你可以寻找那个位置,创建一个新的解码器,然后开始读取值从那里开始。
查看相关问题:Efficient Go serialization of struct to disk
除上述之外,我建议使用中间结构来排除gob header:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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 } |
除了很棒的 icza 答案之外,您还可以使用以下技巧将已写入数据的 gob 文件附加到 gob 文件中:当第一次附加时写入并丢弃第一个编码:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 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) } } |