实现简单的K-V存储

本节你将学习使用Go实现K-V存储的简单实现,其背后的思想是易于理解的:尽可能快速地发出请求并给出响应,并将其转化成对应的数据结构。

本节代码将实现K-V存储的四个基本功能:

  1. 添加新元素
  2. 基于key删除已有的元素
  3. 给定key查找对应value
  4. 修改key对应的value

我们将这四个功能命名为ADD,DELETE,LOOKUP,CHANGE,完成这四个基本功能,你将会对K-V存储的实现有一个全面的了解。另外,当你输入STOP时整个程序就会停止,输入PRINT命令就会打印出当前K-V存储的内容。

本节的keyValue.go将分为5个代码段解释。

第一部分:

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. "strings"
  7. )
  8. type myElement struct {
  9. Name string
  10. SurName string
  11. Id string
  12. }
  13. var DATA = make(map[string]myElement)

我们使用原生的Go map来实现K-V存储,因为内置的数据结构往往执行效率更高。map变量被声明为全局变量,其k为string类型,v为myElement类型,myElement是自定义的结构体。

第二部分代码:

  1. func ADD(k string,n myElement) bool {
  2. if k == "" {
  3. return false
  4. }
  5. if LOOKUP(k) == nil {
  6. DATA[k] = n
  7. return true
  8. }
  9. return false
  10. }
  11. func DELETE(k string) bool {
  12. if LOOKUP(k) != nil {
  13. delete(DATA, k)
  14. return true
  15. }
  16. return false
  17. }

这部分代码实现了命令行ADDDELETE,用户在执行ADD命令时,如果没有携带足够的参数,我们要保证该操作不会失败,意味着myElement中对应的字段为空字符串。然而如果你要添加的key已经存在了,就会报错(K-V存储不允许重复key出现)而不是修改对应的值。

第三部分代码:

  1. func LOOKUP(k string) *myElement {
  2. _, ok := DATA[k]
  3. if ok {
  4. n := DATA[k]
  5. return &n
  6. } else {
  7. return nil
  8. }
  9. }
  10. func CHANGE(k string, n myElement) bool {
  11. DATA[k] = n
  12. return true
  13. }
  14. func PRINT() {
  15. for k, v := range DATA {
  16. fmt.Printf("key: %s value: %v",k,v)
  17. }
  18. }

该代码段实现了LOOKUPCHANGE功能,如果你要修改的key不存储,程序会自动将其存储。

PRINT命令能够打印出目前所有K-V存储的内容。

第四部分代码:

  1. func main() {
  2. scanner := bufio.NewScanner(os.Stdin)
  3. for scanner.Scan() {
  4. text := scanner.Text()
  5. text = strings.TrimSpace(text)
  6. tokens := strings.Fields(text)
  7. switch len(tokens) {
  8. case 0:
  9. continue
  10. case 1:
  11. tokens = append(tokens,"")
  12. tokens = append(tokens,"")
  13. tokens = append(tokens,"")
  14. tokens = append(tokens,"")
  15. case 2:
  16. tokens = append(tokens,"")
  17. tokens = append(tokens,"")
  18. tokens = append(tokens,"")
  19. case 3:
  20. tokens = append(tokens,"")
  21. tokens = append(tokens,"")
  22. case 4:
  23. tokens = append(tokens,"")
  24. }

该部分代码读取用户的输入。首先,for循环将保证程序一直等待用户的输入,接下来使用tokens切片保证至少有5个元素的输入,即使只有ADD命令需要5个参数。如果用户不想在使用ADD命令时出现空字符串值,就需要这样输入:ADD aKey Field1 Field2 Field3

最后一部分代码:

  1. switch tokens[0] {
  2. case "PRINT":
  3. PRINT()
  4. case "STOP":
  5. return
  6. case "DELETE":
  7. if !DELETE(tokens[1]) {
  8. fmt.Println("Delete operations failed")
  9. }
  10. case "ADD":
  11. n := myElement{tokens[2],tokens[3],tokens[4]}
  12. if !ADD(tokens[1],n) {
  13. fmt.Println("Add operation failed")
  14. }
  15. case "LOOKUP":
  16. n := LOOKUP(tokens[1])
  17. if n != nil {
  18. fmt.Printf("%v\n",n)
  19. }
  20. case "CHANGE":
  21. n := myElement{tokens[2],tokens[3],tokens[4]}
  22. if !CHANGE(tokens[1],n) {
  23. fmt.Println("Update operation failed")
  24. }
  25. default:
  26. fmt.Println("Unknown command - please try again!")
  27. }
  28. }
  29. }

这部分代码处理用户的输入。switch的使用使得程序的逻辑设计看上去十分清晰,能够将程序员从冗余的if...else中拯救出来。

执行keyValue.go得到如下输出:

  1. $ go run keyValue.go
  2. UNKNOWN
  3. Unknown command - please try again!
  4. ADD 123 1 2 3
  5. ADD 234 2 3 4
  6. ADD 234
  7. Add operation failed
  8. ADD 345
  9. PRINT
  10. key: 123 value: {1 2 3}key: 234 value: {2 3 4}key: 345 value: { }
  11. CHANGE 345 3 4 5
  12. PRINT
  13. key: 345 value: {3 4 5}key: 123 value: {1 2 3}key: 234 value: {2 3 4}
  14. DELETE 345
  15. PRINT
  16. key: 123 value: {1 2 3}key: 234 value: {2 3 4}
  17. ADD 567 -5 -6 -7
  18. PRINT
  19. key: 123 value: {1 2 3}key: 234 value: {2 3 4}key: 567 value: {-5 -6 -7}
  20. CHANGE 567
  21. PRINT
  22. key: 567 value: { }key: 123 value: {1 2 3}key: 234 value: {2 3 4}
  23. STOP

学习第8章后你将学会如何支持K-V存储的数据持久化功能。然而,在单用户应用使用goroutines和channel是没有任何实际意义的,但是如果你是通过TCP/IP实现K-V存储,那么使用goroutines和channel会帮助你实现多连接、服务多用户功能。你将在第9、10章学习goroutines和channel的知识,并且在12、13章学习网络编程的相关知识,尽情期待吧!