08.10 从磁盘加载和保存数据

你还记得第4节的keyValue.go应用吗?复合类型的使用,它还远远没有完成,所以在本节中,你将学习如何将键值存储的数据保存在磁盘上,以及如何在下一次启动应用程序时将其加载到内存。

我们准备创建两个新函数,save()保存数据到磁盘,load()从磁盘加载数据。因此,我们将用diff(1) Unix命令行程序显示keyValue.gokvSaveLoad.go之间的代码差异。

当你希望发现两个文本文件之间的差异时,diff(1) Unix命令行实用程序非常方便。通过在Unix shell命令行上执行man 1 diff,你可以了解更多关于它的信息。

如果你考虑这里要实现的任务,你将认识到你需要的是一种简单的方法来将Go映射的内容保存到磁盘,以及一种方法来加载文件中的数据并将其放入Go映射。

将数据转换为字节流的过程称为序列化。读取数据文件并将其转换为对象的过程称为反序列化。encoding/gob标准Go包将用于kvSaveLoad.go程序。它将有助于序列化和反序列化过程。encoding/gob包使用gob格式存储其数据。这种格式的官方名称是流编码。gob格式的好处是,Go完成了所有的繁琐工作,因此你不必担心编码和解码阶段。

其他Go包可以帮助你序列化和反序列化数据,encoding/xml使用XML格式,encoding/json使用JSON格式。

下面的输出将显示kvSaveLoad.gokeyValue.go之间的代码更改,不包括save()load()函数的实现,这些函数将在这里完整地展示:

$ diff keyValue.go kvSaveLoad.go
4a5
>. "encoding/gob"
16a18,55
> var DATAFILE = "/tmp/dataFile.gob"
> func save() error {
>
>    return nil
> }
>
> func load() error {
>
> }
59a99,104
>
>.   err := load()
>    if err != nil {
>        fmt.Println(err)
>    }
>
88a134,137
>            err = save()
>            if err != nil {
>                 fmt.Println(err)
>            }

diff(1)输出的一个重要部分是DATAFILE全局变量的定义,该变量保存键值存储使用的文件路径。除此之外,还可以看到load()函数的调用位置以及save()函数的调用位置。load()函数首先在main()函数中使用,而save()函数在用户发出STOP命令时执行。

save()函数实现如下:

func save() error {
    fmt.Println("Saving", DATAFILE)
    err := os.Remove(DATAFILE)
    if err != nil {
        fmt.Println(err)
    }

    saveTo, err := os.Create(DATAFILE)
    if err != nil {
        fmt.Println("Cannot create", DATAFILE)
        return err
    }
    defer saveTo.Close()

    encoder := gob.NewEncoder(saveTo)
    err = encoder.Encode(DATA)
    if err != nil {
        fmt.Println("Cannot save to", DATAFILE)
        return err
    }
    return nil
}

注意,save()函数做的第一件事是使用os.Remove()函数删除现有数据文件,以便稍后创建它。

save()函数所做的最关键的任务之一是确保你可以实际创建并写入所需的文件。尽管有很多方法可以做到这一点,但是save()函数是最简单的方法,即检查os.Create()函数返回的错误值。如果该值不是nil,那么就会出现问题,save()函数在不保存任何数据的情况下结束。

load()函数实现如下:

func load() error {
    fmt.Println("Loading", DATAFILE)
    loadFrom, err := os.Open(DATAFILE)
    defer loadFrom.Close()
    if err != nil {
        fmt.Println("Empty key/value store!")
        return err
    }

    decoder := gob.NewDecoder(loadFrom)
    decoder.Decode(&DATA)
    return nil
}

load()函数的任务之一是确保要读取的文件确实存在,并且可以毫无问题地读取它。load()函数再次使用最简单的方法,即查看os.Open()函数的返回值。如果返回的错误值等于nil,则一切正常。

在读取数据之后关闭文件也很重要,因为稍后save()函数将覆盖该文件。文件的释放由defer loadFrom.Close()语句完成。

执行kvSaveLoad.go会产生如下的输出:

$ go run kvSaveLoad.go
Loading /tmp/dataFile.gob
Empty key/value store!
open /tmp/dataFile.gob: no such file or directory
ADD 1 2 3
ADD 4 5 6
STOP
Saving /tmp/dataFile.gob
remove /tmp/dataFile.gob: no such file or directory
$ go run kvSaveLoad.go
Loading /tmp/dataFile.gob
PRINT
key: 1 value: {2 3 }
key: 3 value: {5 6 }
DELETE 1
PRINT
key: 4 value: {5 6 }
STOP
Saving /tmp/dataFile.gob
rMacBook:code mtsouk$ go run kvSaveLoad.go
Loading /tmp/dataFile.gob
PRINT
key: 4 value: {5 6 }
STOP
Saving /tmp/dataFile.gob
$ ls -l /tmp/dataFile.gob
-rw-r--r-- 1 mtsouk wheel 80 Jan 22 11:22 /tmp/dataFile.gob
$ file /tmp/dataFile.gob
/tmp/dataFile.gob: data

在第13章,网络编程—构建服务器和客户端,你将看到键值存储的最终版本,它将能够在TCP/IP连接上运行,并将使用goroutines服务于多个网络客户端。

Last updated