用于持久化 BTree 的内置库,相当于 Java 的 `FileChannel` 和 `ByteBuffer`
Built-in libraries to persist a BTree, equivalent to Java's `FileChannel` and `ByteBuffer`
所以,我按照 CLRS 算法在 Java 中编写了这个持久性 BTree。
我使用 FileChannel
和 ByteBuffer
将树存储在文件中,在需要时读取和写入节点。
我试着寻找如何在 Go 中存储这样一个 BTree,并发现了 os.File
,我认为它可以像 Java 的 FileChannel
一样使用。
但是,我找不到 ByteBuffer
的等效项。我查看了 bytes.Buffer
,我明白了它是如何工作的,但是它没有 ByteBuffer
的便利 putInt
、putDouble
等...
我是否必须自己实现那些将整数和双精度数转换为字节数组的函数?我也看了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()
函数直接写入/ 在不使用任何内存缓冲区的情况下从您的文件中读取。
查看此示例编码和解码 int32
和 float64
值:
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 虽然在存储和速度方面的性能会降低,但序列化格式会更加人性化(更易读/可编辑),并且与其他应用程序/技术兼容。
所以,我按照 CLRS 算法在 Java 中编写了这个持久性 BTree。
我使用 FileChannel
和 ByteBuffer
将树存储在文件中,在需要时读取和写入节点。
我试着寻找如何在 Go 中存储这样一个 BTree,并发现了 os.File
,我认为它可以像 Java 的 FileChannel
一样使用。
但是,我找不到 ByteBuffer
的等效项。我查看了 bytes.Buffer
,我明白了它是如何工作的,但是它没有 ByteBuffer
的便利 putInt
、putDouble
等...
我是否必须自己实现那些将整数和双精度数转换为字节数组的函数?我也看了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()
函数直接写入/ 在不使用任何内存缓冲区的情况下从您的文件中读取。
查看此示例编码和解码 int32
和 float64
值:
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 虽然在存储和速度方面的性能会降低,但序列化格式会更加人性化(更易读/可编辑),并且与其他应用程序/技术兼容。