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

时间:2017-04-04 05:44:44

标签: serialization go encoding binary gob

是否可以使用Gob编码将结构串联附加到使用追加的同一文件?它适用于写入,但在使用解码器多次读取时,我遇到了:

extra data in buffer

所以我想知道这是否可行,或者我是否应该使用类似JSON的东西来代替每行附加JSON文档。因为替代方法是序列化一个切片,但是然后再次读取它会破坏追加的目的。

3 个答案:

答案 0 :(得分:3)

gob包不是以这种方式使用的。 gob流必须由单个gob.Encoder编写,并且还必须由单个gob.Decoder读取。

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

  

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

这是编码器/解码器的状态 - 关于它们被传输的类型和方式 - 随后的新编码器/解码器将不会(不能)分析“先前”流以重建相同的状态并继续在哪里之前的编码器/解码器已关闭。

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

此外,您可以创建gob.Encoder并写入文件,然后稍后创建新的gob.Encoder,并附加到同一文件您必须使用2 gob.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软件包来执行您想要执行的操作,则必须稍微修改,增强您的编码/解码过程。当使用新编码器时,你必须以某种方式标记边界(因此,在解码时,你将知道你必须创建一个新的解码器来读取后续值。)

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

  • 在继续写入值之前,您可以写出一个数字,一个计数,这个数字将告诉您使用当前编码器写入了多少个值。
  • 如果您不想或不想知道当前编码器将写入多少个值,您可以选择在写入时写出特殊的编码器结尾值用当前编码器写入更多值。解码时,如果遇到这个特殊的编码器结束值,您就会知道必须创建一个新的解码器才能读取更多值。

有些事情需要注意:

  • 如果只使用一个编码器,gob包效率最高,大多数压缩,因为每次创建和使用新编码器时,都必须重新输入类型规范-transmitted,导致更多开销,并使编码/解码过程变慢。
  • 如果您从头开始读取整个文件直到您想要的值,那么您无法在数据流中搜索任何值。请注意,即使您使用其他格式(例如JSON或XML),这也有些适用。

如果你想寻求功能,你需要单独管理一个索引文件,它会告诉新的编码器/解码器从哪个位置开始,所以你可以寻找那个位置,创建一个新的解码器,并从那里开始读取值。

检查相关问题:Efficient Go serialization of struct to disk

答案 1 :(得分:0)

除了很好的icza答案外,您还可以使用以下技巧将具有已写入数据的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)
    }
}

答案 2 :(得分:0)

除上述内容外,我建议使用中间结构来排除料滴头:

ui <- 
  fluidPage(
    fluidRow(
      column(
        12,
        bsCollapse(
          id="instructionsOutput", 
          open="instructionsPanel",
          bsCollapsePanel(
            "instructionsPanel",
            "This is a panel with text",
            style="info"
          )
        )
      ),
      column(
        12,
        actionButton(
          "p1Button", 
          "Expand/Collapse Text"
        )
        , align = "center"
        , style = "margin-bottom: 10px;"
        , style = "margin-top: -10px;"
      )
    ),
    sidebarLayout(
      sidebarPanel(),
      mainPanel()
    )
  )

server <- function(input, output, session) {
  observeEvent(input$p1Button, ({
    updateCollapse(
      session, "instructionsOutput",
      open="instructionsPanel",
      close="instructionsPanel"
    )  
  })
  )

}

shinyApp(ui, server)