是否可以使用Gob编码将结构串联附加到使用追加的同一文件?它适用于写入,但在使用解码器多次读取时,我遇到了:
extra data in buffer
所以我想知道这是否可行,或者我是否应该使用类似JSON的东西来代替每行附加JSON文档。因为替代方法是序列化一个切片,但是然后再次读取它会破坏追加的目的。
答案 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,导致更多开销,并使编码/解码过程变慢。如果你想寻求功能,你需要单独管理一个索引文件,它会告诉新的编码器/解码器从哪个位置开始,所以你可以寻找那个位置,创建一个新的解码器,并从那里开始读取值。
答案 1 :(得分:0)
除了很好的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)
}
}
答案 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)