使用 Gob 以追加方式将日志写入文件
Use Gob to write logs to a file in an append style
是否可以使用 Gob 编码将结构串联附加到同一文件中?它适用于写作,但当我多次使用解码器阅读时,我 运行 进入:
extra data in buffer
所以我首先想知道这是否可行,或者我是否应该使用 JSON 之类的东西来改为在每行基础上附加 JSON 文档。因为另一种方法是序列化一个切片,但再次将其作为一个整体读取会破坏追加的目的。
gob
package wasn't designed to be used this way. A gob stream has to be written by a single gob.Encoder
, and it also has to be read by a single gob.Decoder
.
这是因为 gob
包不仅序列化您传递给它的值,它还传输数据来描述它们的类型:
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.
这是编码器/解码器的状态——关于什么类型以及它们是如何传输的——,后续的新编码器/解码器将不会(不能)分析"preceeding"流来重建相同的状态并从上一个编码器/解码器停止的地方继续。
当然,如果您创建一个 gob.Encoder
,您可以使用它来序列化任意数量的值。
您也可以创建一个 gob.Encoder
并写入一个文件,然后再创建一个新的 gob.Encoder
,并追加到同一个文件,但是 你必须使用 2 gob.Decoder
s 来读取这些值,与编码过程完全匹配。
作为演示,我们来举个例子。此示例将写入内存缓冲区 (bytes.Buffer
)。 2 个后续编码器将写入它,然后我们将使用 2 个后续解码器读取值。我们将写入此结构的值:
type Point struct {
X, Y int
}
为了简短、紧凑的代码,我使用这个 "error handler" 函数:
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 当您不使用当前编码器写入更多值时的值。解码时,如果你遇到这个特殊的 end-of-encoder 值,你就会知道你必须创建一个新的解码器才能读取更多值。
这里有几点需要注意:
- 如果只使用一个编码器,
gob
包最高效,最紧凑,因为每次创建和使用新编码器时,类型规范将不得不重新传输,导致更多开销,并使编码/解码过程变慢。
- 你不能在数据流中查找,如果你从头读取整个文件直到你想要的值,你只能解码任何值。请注意,即使您使用其他格式(例如 JSON 或 XML),这在某种程度上也适用。
如果你想寻找功能,你需要单独管理一个 index 文件,它会告诉新的编码器/解码器从哪个位置开始,所以你可以寻找那个位置,创建一个新的解码器,并从那里开始读取值。
查一个相关问题:
除了很棒的 icza 答案之外,您还可以使用以下技巧将已写入数据追加到 gob 文件:第一次追加时写入并丢弃第一个编码:
- 像往常一样创建文件编码 gob(首先编码写入 headers)
- 关闭文件
- 打开文件进行 追加
- 使用中间编写器编码虚拟结构(写入 headers)
- 重置写入器
- 照常编码 gob(不写入 headers)
示例:
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)
}
}
除上述之外,我建议使用中间结构来排除采空区header:
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 编码将结构串联附加到同一文件中?它适用于写作,但当我多次使用解码器阅读时,我 运行 进入:
extra data in buffer
所以我首先想知道这是否可行,或者我是否应该使用 JSON 之类的东西来改为在每行基础上附加 JSON 文档。因为另一种方法是序列化一个切片,但再次将其作为一个整体读取会破坏追加的目的。
gob
package wasn't designed to be used this way. A gob stream has to be written by a single gob.Encoder
, and it also has to be read by a single gob.Decoder
.
这是因为 gob
包不仅序列化您传递给它的值,它还传输数据来描述它们的类型:
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.
这是编码器/解码器的状态——关于什么类型以及它们是如何传输的——,后续的新编码器/解码器将不会(不能)分析"preceeding"流来重建相同的状态并从上一个编码器/解码器停止的地方继续。
当然,如果您创建一个 gob.Encoder
,您可以使用它来序列化任意数量的值。
您也可以创建一个 gob.Encoder
并写入一个文件,然后再创建一个新的 gob.Encoder
,并追加到同一个文件,但是 你必须使用 2 gob.Decoder
s 来读取这些值,与编码过程完全匹配。
作为演示,我们来举个例子。此示例将写入内存缓冲区 (bytes.Buffer
)。 2 个后续编码器将写入它,然后我们将使用 2 个后续解码器读取值。我们将写入此结构的值:
type Point struct {
X, Y int
}
为了简短、紧凑的代码,我使用这个 "error handler" 函数:
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 当您不使用当前编码器写入更多值时的值。解码时,如果你遇到这个特殊的 end-of-encoder 值,你就会知道你必须创建一个新的解码器才能读取更多值。
这里有几点需要注意:
- 如果只使用一个编码器,
gob
包最高效,最紧凑,因为每次创建和使用新编码器时,类型规范将不得不重新传输,导致更多开销,并使编码/解码过程变慢。 - 你不能在数据流中查找,如果你从头读取整个文件直到你想要的值,你只能解码任何值。请注意,即使您使用其他格式(例如 JSON 或 XML),这在某种程度上也适用。
如果你想寻找功能,你需要单独管理一个 index 文件,它会告诉新的编码器/解码器从哪个位置开始,所以你可以寻找那个位置,创建一个新的解码器,并从那里开始读取值。
查一个相关问题:
除了很棒的 icza 答案之外,您还可以使用以下技巧将已写入数据追加到 gob 文件:第一次追加时写入并丢弃第一个编码:
- 像往常一样创建文件编码 gob(首先编码写入 headers)
- 关闭文件
- 打开文件进行 追加
- 使用中间编写器编码虚拟结构(写入 headers)
- 重置写入器
- 照常编码 gob(不写入 headers)
示例:
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)
}
}
除上述之外,我建议使用中间结构来排除采空区header:
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
}