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