数据存储可以分为三大类:文件存储、关系型的数据库(SQL)和非关系型的数据库(NoSQL)。本文主要讲述文件存储的实现方式。
文件存储根据不同的文件实现不同的存储方式:普通文件(如txt读写)、CSV文件、数据的序列化和持久化。
普通文件的读写可以使用os或io/ioutil包实现,两者的实现方式如下所示。
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
// 使用ioutil的WriteFile对文件执行写入操作
data := []byte("Hello World!\n")
err := ioutil.WriteFile("data1.txt", data, 0644)
if err != nil {
panic(err)
}
// 使用ioutil的ReadFile对文件执行读取操作
read1, _ := ioutil.ReadFile("data1.txt")
fmt.Print(string(read1))
// 使用os的Create创建文件对象
file1, _ := os.Create("data2.txt")
// 使用defer,当程序执行结束将自动关闭文件对象file1
defer file1.Close()
// 使用文件对象file1对文件写入数据
bytes, _ := file1.Write(data)
fmt.Printf("Wrote %d bytes to file\n", bytes)
// 使用os的Open打开文件data2.txt,生成文件对象file2
file2, _ := os.Open("data2.txt")
defer file2.Close()
// 根据数据data的长度定义相应的数组
read2 := make([]byte, len(data))
// 由文件对象file2调用Read读取文件的数据内容,并将数据加载到数组read2
bytes, _ = file2.Read(read2)
fmt.Printf("Read %d bytes from file\n", bytes)
fmt.Println(string(read2))
}
CSV 格式是一种文件格式,它可以让文本编辑器非常方便地读写由文本和数字组成的表格数据。CSV 的应用非常广泛,包括微软的 Excel 和苹果的 Numbers 在内的绝大多数电子表格程序都支持 CSV 格式,因此包括 Go 在内的很多编程语言都提供了能够生成和处理 CSV 文件数据的函数库。对 Go 语言来说,CSV 文件可以通过encoding/csv包进行操作,实现代码如下。
package main
import (
"encoding/csv"
"fmt"
"os"
"strconv"
)
type Post struct {
Id int
Content string
Author string
}
func main() {
// 创建文件posts.csv
csvFile, err := os.Create("posts.csv")
if err != nil {
panic(err)
}
defer csvFile.Close()
// 创建数据
allPosts := []Post{
Post{Id: 1, Content: "Hello World!", Author: "Sau Sheong"},
Post{Id: 2, Content: "Bonjour Monde!", Author: "Pierre"},
Post{Id: 3, Content: "Hola Mundo!", Author: "Pedro"},
Post{Id: 4, Content: "Greetings Earthlings!", Author: "Sau Sheong"},}
// 使用csv的NewWriter方法对文件csvFile进行写入操作
writer := csv.NewWriter(csvFile)
// 遍历allPosts的每行数据,将每行数据依次写入CSV文件
for _, post := range allPosts {
// 将strconv.Itoa(post.Id)转换成字符串格式
// 变量line以数组的形式表示
line := []string{strconv.Itoa(post.Id), post.Content, post.Author}
// 将数组line写入CSV文件
err := writer.Write(line)
if err != nil {
panic(err)
}
}
// 调用Flush方法来保证缓冲区中的所有数据都已经被正确地写入文件里面
writer.Flush()
// ………………………………………………………………………………………分界线………………………………………………………………………………………
// 打开CSV文件
file, err := os.Open("posts.csv")
if err != nil {
panic(err)
}
defer file.Close()
// 使用csv的NewWriter方法对文件csvFile进行读取操作
reader := csv.NewReader(file)
// 设置每行数据里面的字段数量
// 如果FieldsPerRecord的值为正数,并且CSV文件里面读取出的字段数量少于这个值时,Go就会抛出一个错误。
// 如果FieldsPerRecord的值为0,那么读取器就会将读取到的第一条记录的字段数量用作FieldsPerRecord的值。
// 如果FieldsPerRecord的值为负数,即使在读取过程中发现数据里面缺少了某些字段,读取进程也不会被中断。
reader.FieldsPerRecord = -1
// 一次读取所有数据
// 多次读取可使用Read()
record, err := reader.ReadAll()
if err != nil {
panic(err)
}
// 将已读取的数据依次输出
var posts []Post
for _, item := range record {
// 将字段Id转化为整型
id, _ := strconv.ParseInt(item[0], 0, 0)
post := Post{Id: int(id), Content: item[1], Author: item[2]}
posts = append(posts, post)
}
fmt.Println(posts[0].Id)
fmt.Println(posts[0].Content)
fmt.Println(posts[0].Author)
}
go的encoding/gob包用于管理由 gob 组成的流(stream),这是一种在编码器(encoder)和解码器(decoder)之间进行交换的二进制数据,这种数据原本是为序列化以及数据传输而设计的,但它也可以用于对数据进行持久化,并且为了让用户能够方便地对文件进行读写,编码器和解码器一般都会分别包裹起程序的写入器以及读取器。简单来说,go的encoding/gob包类似python的pickle模块,两者实现的功能是相同的。
但两者对比发现,encoding/gob包的使用方法却没有python的便捷,具体的使用过程如下所示。
package main
import (
"bytes"
"encoding/gob"
"fmt"
"io/ioutil"
)
type Post struct {
Id int
Content string
Author string
}
func store(data interface{}, filename string) {
// 定义字节缓冲区对象buffer
// 这是拥有Read方法和Write方法的可变长度(variable-sized)字节缓冲区
// 换句话说,bytes.Buffer既是读取器也是写入器。
buffer := new(bytes.Buffer)
// 对缓冲区对象buffer设置编码,生成编码器encoder
encoder := gob.NewEncoder(buffer)
// 将数据data写入编码器encoder(字节缓冲区对象buffer)
err := encoder.Encode(data)
if err != nil {
panic(err)
}
// 将缓冲区对象buffer的数据内容以字节的方式写入文件,生成二进制数据文件
err = ioutil.WriteFile(filename, buffer.Bytes(), 0600)
if err != nil {
panic(err)
}
}
func load(data interface{}, filename string) {
// 读取文件filename的数据内容
raw, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
// 定义字节缓冲区对象buffer,并将文件内容raw加载到字节缓冲区对象buffer
buffer := bytes.NewBuffer(raw)
// 在字节缓冲区对象buffer设置解码器dec
dec := gob.NewDecoder(buffer)
// 将解码后的数据写入参数data
err = dec.Decode(data)
if err != nil {
panic(err)
}
}
func main() {
post := Post{Id: 1, Content: "Hello World!", Author: "Sau Sheong"}
store(post, "post1")
var postRead Post
load(&postRead, "post1")
fmt.Println(postRead.Id)
}