Go 学习笔记(三十八)数据存储 (3)——gob 对象序列化

本文原创地址:博客园骏马金龙Go Web:数据存储 (3)——gob 对象序列化

序列化持久存储 gob

encoding/gob 包用于编码器和解码器之间进行二进制流的数据交换,例如发送端和接收端之间交换数据。也能用来实现对象序列化,并持久保存的功能,然后再需要的时候加载并解码成原始数据。

gob 包的用法很简单,但有必要去了解它的背景知识。

下面是序列化到文件中的函数代码:

func storeToGob(data interface{}, filename string) {
	buffer := new(bytes.Buffer)
	encoder := gob.NewEncoder(buffer)
	err := encoder.Encode(data)
	if err != nil {
		panic(err)
	}
	err = ioutil.WriteFile(filename, buffer.Bytes(), 0600)
	if err != nil {
		panic(err)
	}
}

其中 gob.NewEncoder()函数用来生成一个 gob 的流编码器。通过编码器的 Encode() 方法,可以将数据编码成二进制格式,Encode() 方法的参数类型是interface{},所以 storeToGob() 函数的第一个参数也指定为此类型可以将任意数据结构序列化。然后只需将序列化到 buffer 中的数据写入到文件中即可。

需要注意的是上面使用 ioutil.WriteFile()函数会在文件存在时进行截断,文件不存在时以给定权限 (上面给的权限是 600) 进行文件创建。

另外, gob 不会序列化指针,它会找到指针所指向的数据对象,并对数据对象进行序列化

因为这个函数接受任意类型的数据结构,所以可以将 map 类型的 PostById 和 PostByAuthor 进行序列化,也可以将 Post 类型的单篇文章进行序列化。

storeToGob(PostById, "d:/PostById.gob")
storeToGob(PostByAuthor, "d:/PostByAuthor.gob")
storeToGob(post3, "d:/post3.gob")

序列化之后,可以加载序列化后的文件进行解码。加载 gob 文件并解码二进制数据的函数如下:

func load(data interface{}, filename string) {
	raw, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	buffer := bytes.NewBuffer(raw)
	dec := gob.NewDecoder(buffer)
	err = dec.Decode(data)
	if err != nil {
		panic(err)
	}
}

逻辑很简单,从文件中读取数据,并解码后保存到 data 中。

唯一需要注意的是解码器的解码方法 Decode() 的参数虽然是interface{}类型的,但却要求只能是指针类型。如果传参时传递的是非指针类型,将报错。

例如,分别解码前文保存的 3 个 gob 文件,并分别保存到对应的数据结构中:

load(&PostById, "d:/PostById.gob")
fmt.Println(PostById[1])
fmt.Println(PostById[2])

load(&PostByAuthor, "d:/PostByAuthor.gob")
for _, post := range PostByAuthor["userA"] {
	fmt.Println(post)
}

var post33 *Post
load(&post33, "d:/post3.gob")
fmt.Println(post33)

注意上面调用 load() 函数时,传递的第一个参数都是指针类型的。post33 变量自身就是指针,所以上面load(post33,"d:/post3.gob")也是可行的,但传递post33&post33的结果是不一样的,如下:

// load(post33, "d:/post3.gob")
{3 Hello 3 userC}

// load(&post33, "d:/post3.gob")
&{3 Hello 3 userC}

现在数据又回到了内存存储结构上,可以进行正常的增、删、改、查等操作。

下面是完整的代码:

package main

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"io/ioutil"
)

type Post struct {
	Id      int
	Content string
	Author  string
}

var PostById map[int]*Post
var PostByAuthor map[string][]*Post

func store(post *Post) {
	PostById[post.Id] = post
	PostByAuthor[post.Author] = append(PostByAuthor[post.Author], post)
}

func storeToGob(data interface{}, filename string) {
	buffer := new(bytes.Buffer)
	encoder := gob.NewEncoder(buffer)
	err := encoder.Encode(data)
	if err != nil {
		panic(err)
	}
	err = ioutil.WriteFile(filename, buffer.Bytes(), 0600)
	if err != nil {
		panic(err)
	}
}

func load(data interface{}, filename string) {
	raw, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	buffer := bytes.NewBuffer(raw)
	dec := gob.NewDecoder(buffer)
	err = dec.Decode(data)
	if err != nil {
		panic(err)
	}
}

func main() {
	PostById = make(map[int]*Post)
	PostByAuthor = make(map[string][]*Post)
	post1 := &Post{Id: 1, Content: "Hello 1", Author: "userA"}
	post2 := &Post{Id: 2, Content: "Hello 2", Author: "userB"}
	post3 := &Post{Id: 3, Content: "Hello 3", Author: "userC"}
	post4 := &Post{Id: 4, Content: "Hello 4", Author: "userA"}
	store(post1)
	store(post2)
	store(post3)
	store(post4)

	storeToGob(PostById, "d:/PostById.gob")
	storeToGob(PostByAuthor, "d:/PostByAuthor.gob")
	storeToGob(post3, "d:/post3.gob")

	PostById = map[int]*Post{}
	PostByAuthor = map[string][]*Post{}

	var post33 *Post
	load(&post33, "d:/post3.gob")
	fmt.Println(post33)

	load(&PostById, "d:/PostById.gob")
	fmt.Println(PostById[1])
	fmt.Println(PostById[2])

	load(&PostByAuthor, "d:/PostByAuthor.gob")
	for _, post := range PostByAuthor["userA"] {
		fmt.Println(post)
	}
}

上一篇 Go 学习笔记(三十七)数据存储 (2)——CSV 文件
Go 学习笔记(目录)
下一篇 Go 学习笔记(三十九)深入剖析 Go template