19.5. 数据存储: gob

未匹配的标注

[ 本节的代码能在 goto_v2 目录中的 store.go 和 main.go 中找到 ]

当 goto 进程(运行在 8080 端口的 web 服务器)结束时,这个迟早并一定会发生, map 保存在内存中的 URLs 将会丢失。要保存我们 map 中的数据,我需要将它保存到一个磁盘文件中。我们将修改 URLStore ,用于将它的数据写入到一个文件,并且在 goto 启动的时候恢复这个数据。 为了实现它,我们将使用 Go 的 encoding/gob 包:这是一个序列化与反序列化包,它将数据结构转换成 bytes 数组(或者更准确的说是一个切片),反之依然(参见: 章节 12.11 )。

使用 gob 包的 NewEncoderNewDecoder 函数,你来决定向它写入数据或者从它读取数据。由 Encoder 与 Decoder 所产生的对象提供了 Encode 和 Decode 的方法,用于向文件中写入和读取 Go 数据结构。顺便说一下: Encoder 也能实现 Writer 接口,Decoder 也同样可以实现 Reader 接口。我们将向 URLStore 添加一个新的 file 字段( *os.File 类型 ), 它将是一个可以用于写入和读取的打开文件的句柄 。


type URLStore struct {

    urls map[string]string

    mu sync.RWMutex

    file *os.File

}

当我们实例化 URLStore 的时候,我们将调用这个文件 store.gob ,并将它的名称作为参数: var store = NewURLStore("store.gob")

现在我们必须调整我们的 NewURLStore 函数:


func NewURLStore(filename string) *URLStore {

    s := &URLStore{urls: make(map[string]string)}

    f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)

    if err != nil {

        log.Fatal("URLStore:", err)

    }

    s.file = f

    return s

}

NewURLStore 函数现在得到一个 filename 参数,打开文件( 参见 12 章 ),并且在我们的 URLStore 的变量 store 的 file 字段中保存 *os.File 的值,这里被称为 s

调用 OpenFile 可能会失败(例如,我们的磁盘文件可能被删除或重命名)

它能返回一个错误 err,注意 Go 是如何处理的:


f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)

if err != nil {

    log.Fatal("URLStore:", err)

}

当 err 不是 nil ,这意味着真的存在一个错误,我们停止程序,记录一条消息。这是处理的一种方式,大多数情况下,错误会返回给调用函数,但是这种测试错误的方式在 Go 中无处不在。在 } 之后,我们确信文件已经打开了。

我们用可写的方式打开文件,更确切的说是在追加模式下。每次在我们的程序中创建一对新的 (短、长) URL ,我们将通过 gob 将它保存在 store.gob 文件中。

为此,我们定义一个新的结构体类型 record :


type record struct {

    Key, URL string

}

以及一个新的 save 方法,它将给定的 key 和 url 作为一个 gob 编码的 record 写入到磁盘。


func (s *URLStore) save(key, url string) error {

    e := gob.NewEncoder(s.file)

    return e.Encode(record{key, url})

}

在 goto 启动的时候,我们磁盘上的数据存储必须读取到 URLStore 中,为此,我们有一个 load 方法


func (s *URLStore) load() error {

    if _, err := s.file.Seek(0, 0); err != nil {

        return err

    }

    d := gob.NewDecoder(s.file)

    var err error

    for err == nil {

        var r record

        if err = d.Decode(&r); err == nil {

            s.Set(r.Key, r.URL)

        }

    }

    if err == io.EOF {

        return nil

    }

    return err

}

新的 load 方法将从文件的开头寻找、读取并解码每一条记录,然后使用 Set 方法保存数据到 map 中。再次注意无处不在的错误处理。这个文件的解码是一个无限循环,只要没有错误就会一直继续下去:


for err == nil {

...

}

如果我们收到一个错误,它可能是因为我们刚好解码到最后一条记录,然后遇到一个 io.EOF (文件结束) 错误;如果不是这种情况,则是我们在解码时发生错误,要将 err 返回。这个方法必须添加到 NewURLStore :


func NewURLStore(filename string) *URLStore {

    s := &URLStore{urls: make(map[string]string)}

    f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)

    if err != nil {

        log.Fatal("Error opening URLStore:", err)

    }

    s.file = f

    if err := s.load(); err != nil {

        log.Println("Error loading data in URLStore:", err)

    }

    return s

}

同样的,在 Put 函数中,当我们向我们的 map 添加一对新的 url 时,它也应该立即被保存到数据文件:


func (s *URLStore) Put(url string) string {

    for {

        key := genKey(s.Count())

        if s.Set(key, url) {

            if err := s.save(key, url); err != nil {

                    log.Println("Error saving to URLStore:", err)

              }

            return key

        }

    }

    panic("shouldn't get here")

}

编译并测试第二个版本,或者简单的使用已经存在的可执行文件(译者注:别纠结为什么没有这个,就自己编译吧),并在关闭了 web 服务器之后仍然可以知道所有的短 url (你可以通过在终端窗口执行 CTRL + C 停止这个进程)。

第一次启动 goto 的时候,文件 store.gob 还不存在,所以在加载的时候你会收到一个错误: 2011/09/11 11:08:11 Error loading URLStore: open store.gob: The system cannot find the file specified.

停止进程并重新启动,然后它开始运行。或者你可以在启动 goto 之前,简单的创建一个空的 store. gob 文件。

备注: 当第 2 次启动 goto 的时候,你可能会收到这个错误:


Error loading URLStore: extra data in buffer

这是因为 gob 是一个基于流的协议,不支持重启。 在第 4 版中,我们将使用 json 作为存储协议,来弥补这种情况。

版本 3---添加协程

第 3 版 goto_v3 的代码(在 章节 19.6 中讨论)能在 code_examples\ chapter_19\goto_v3 目录中找到。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/the-way-to-go/1...

译文地址:https://learnku.com/docs/the-way-to-go/1...

上一篇 下一篇
讨论数量: 0
发起讨论 查看所有版本


暂无话题~