# 04.8 实现简单的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个代码段解释。

第一部分：

> ```go
> package main
>
> import (
>    "bufio"
>    "fmt"
>    "os"
>    "strings"
> )
>
> type myElement struct {
>    Name string
>    SurName string
>    Id string
> }
>
> var DATA = make(map[string]myElement)
> ```

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

第二部分代码：

> ```go
> func ADD(k string,n myElement) bool {
>    if k == "" {
>       return false
>    }
>    if LOOKUP(k) == nil {
>       DATA[k] = n
>       return true
>    }
>    return false
> }
>
> func DELETE(k string) bool {
>    if LOOKUP(k) != nil {
>       delete(DATA, k)
>       return true
>    }
>    return false
> }
> ```

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

第三部分代码：

> ```go
> func LOOKUP(k string) *myElement  {
>    _, ok := DATA[k]
>    if ok {
>       n := DATA[k]
>       return &n
>    } else {
>       return nil
>    }
> }
>
> func CHANGE(k string, n myElement) bool {
>    DATA[k] = n
>    return true
> }
>
> func PRINT()  {
>    for k, v := range DATA {
>       fmt.Printf("key: %s value: %v",k,v)
>    }
> }
> ```

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

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

第四部分代码：

> ```go
> func main() {
>    scanner := bufio.NewScanner(os.Stdin)
>    for scanner.Scan() {
>       text := scanner.Text()
>       text = strings.TrimSpace(text)
>       tokens := strings.Fields(text)
>
>       switch len(tokens) {
>       case 0:
>          continue
>       case 1:
>          tokens = append(tokens,"")
>          tokens = append(tokens,"")
>          tokens = append(tokens,"")
>          tokens = append(tokens,"")
>       case 2:
>          tokens = append(tokens,"")
>          tokens = append(tokens,"")
>          tokens = append(tokens,"")
>       case 3:
>          tokens = append(tokens,"")
>          tokens = append(tokens,"")
>       case 4:
>          tokens = append(tokens,"")
>       
>       }
> ```

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

最后一部分代码：

> ```go
> switch tokens[0] {
>       case "PRINT":
>          PRINT()
>       case "STOP":
>          return
>       case "DELETE":
>          if !DELETE(tokens[1]) {
>             fmt.Println("Delete operations failed")
>          }
>       case "ADD":
>          n := myElement{tokens[2],tokens[3],tokens[4]}
>          if !ADD(tokens[1],n) {
>             fmt.Println("Add operation failed")
>          }
>       case "LOOKUP":
>          n := LOOKUP(tokens[1])
>          if n != nil {
>             fmt.Printf("%v\n",n)
>          }
>
>       case "CHANGE":
>          n := myElement{tokens[2],tokens[3],tokens[4]}
>          if !CHANGE(tokens[1],n) {
>             fmt.Println("Update operation failed")
>          }
>
>       default:
>          fmt.Println("Unknown command - please try again!")
>
>       }
>    }
> }
> ```

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

执行`keyValue.go`得到如下输出：

> ```
> $ go run keyValue.go
> UNKNOWN
> Unknown command - please try again!
>
> ADD 123 1 2 3
> ADD 234 2 3 4
> ADD 234
> Add operation failed
> ADD 345
> PRINT
> key: 123 value: {1 2 3}key: 234 value: {2 3 4}key: 345 value: {  }
> CHANGE 345 3 4 5
> PRINT
> key: 345 value: {3 4 5}key: 123 value: {1 2 3}key: 234 value: {2 3 4}
> DELETE 345
> PRINT
> key: 123 value: {1 2 3}key: 234 value: {2 3 4}
> ADD 567 -5 -6 -7
> PRINT
> key: 123 value: {1 2 3}key: 234 value: {2 3 4}key: 567 value: {-5 -6 -7}
> CHANGE 567
> PRINT
> key: 567 value: {  }key: 123 value: {1 2 3}key: 234 value: {2 3 4}  
> STOP
> ```

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