用于持久化 BTree 的内置库,相当于 Java 的 `FileChannel` 和 `ByteBuffer`

Built-in libraries to persist a BTree, equivalent to Java's `FileChannel` and `ByteBuffer`

所以,我按照 CLRS 算法在 Java 中编写了这个持久性 BTree。 我使用 FileChannelByteBuffer 将树存储在文件中,在需要时读取和写入节点。

我试着寻找如何在 Go 中存储这样一个 BTree,并发现了 os.File,我认为它可以像 Java 的 FileChannel 一样使用。 但是,我找不到 ByteBuffer 的等效项。我查看了 bytes.Buffer,我明白了它是如何工作的,但是它没有 ByteBuffer 的便利 putIntputDouble 等...

我是否必须自己实现那些将整数和双精度数转换为字节数组的函数?我也看了encoding.binary,但是这个看起来很麻烦。我知道我每次都必须将变量编码为字节数组,然后将该字节数组复制到缓冲区。

在这种情况下会推荐什么结构?

注意 bytes.Buffer 实现了 io.Writer 接口。因此,您可以使用 fmt.Fprintf:

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

format 字符串参数可用于将整数和双精度直接写入缓冲区。有关详细信息,请参阅 here

使用 encoding/gob

使用 encoding/gob 包,您可以将整个树序列化为一系列字节,并通过单个方法调用将它们反序列化。

看这个例子:

type Node struct {
    Data     int
    Children []*Node
}

func (n *Node) String() string {
    buf := &bytes.Buffer{}
    buf.WriteString(fmt.Sprintf("Node[Data: %d, Children: [", n.Data))
    for i, v := range n.Children {
        if i > 0 {
            buf.WriteString(", ")
        }
        buf.WriteString(v.String())
    }
    buf.WriteString("]")
    return buf.String()
}

不需要Node.String()方法,我创建它只是为了轻松打印/验证树。

现在使用 gob 序列化和反序列化树:

root := &Node{
    Data: 1,
    Children: []*Node{
        {Data: 2},
        {Data: 3},
    },
}
fmt.Println(root)

buf := &bytes.Buffer{}
if err := gob.NewEncoder(buf).Encode(root); err != nil {
    panic(err)
}

var root2 *Node
if err := gob.NewDecoder(buf).Decode(&root2); err != nil {
    panic(err)
}
fmt.Println(root2)

输出(在 Go Playground 上尝试):

Node[Data: 1, Children: [Node[Data: 2, Children: [], Node[Data: 3, Children: []]
Node[Data: 1, Children: [Node[Data: 2, Children: [], Node[Data: 3, Children: []]

这里我使用了内存缓冲区(bytes.Buffer), but if you want to save to / load from a file, you don't even need an in-memory buffer, you can directly pass an *os.File value to gob.NewEncoder() and gob.NewDecoder() (as *os.File implements both io.Reader and io.Writer)。

手动序列化/反序列化

另请注意,如果不想(或不能)使用encoding/gob一步完成序列化,也可以使用binary.Write() and binary.Read()函数直接写入/ 在不使用任何内存缓冲区的情况下从您的文件中读取。

查看此示例编码和解码 int32float64 值:

var i int32
var f float64

i, f = 1, 3.14

buf := &bytes.Buffer{}
if err := binary.Write(buf, binary.LittleEndian, i); err != nil {
    panic(err)
}
if err := binary.Write(buf, binary.LittleEndian, f); err != nil {
    panic(err)
}

var i2 int32
var f2 float64
if err := binary.Read(buf, binary.LittleEndian, &i2); err != nil {
    panic(err)
}
if err := binary.Read(buf, binary.LittleEndian, &f2); err != nil {
    panic(err)
}

fmt.Println(i2, f2)

输出(在 Go Playground 上尝试):

1 3.14

同样,您可以将文件直接传递给 binary.Read()binary.Write() 而不是 *bytes.Buffer

非二进制序列化

您还可以使用其他非二进制序列化格式,例如 JSON。 encoding/json 包还可以通过一次调用序列化/反序列化整个树。使用 JSON 虽然在存储和速度方面的性能会降低,但序列化格式会更加人性化(更易读/可编辑),并且与其他应用程序/技术兼容。