04.8 实现简单的K-V存储
本节你将学习使用Go实现K-V存储的简单实现,其背后的思想是易于理解的:尽可能快速地发出请求并给出响应,并将其转化成对应的数据结构。
本节代码将实现K-V存储的四个基本功能:
  1. 1.
    添加新元素
  2. 2.
    基于key删除已有的元素
  3. 3.
    给定key查找对应value
  4. 4.
    修改key对应的value
我们将这四个功能命名为ADD,DELETE,LOOKUP,CHANGE,完成这四个基本功能,你将会对K-V存储的实现有一个全面的了解。另外,当你输入STOP时整个程序就会停止,输入PRINT命令就会打印出当前K-V存储的内容。
本节的keyValue.go将分为5个代码段解释。
第一部分:
1
package main
2
3
import (
4
"bufio"
5
"fmt"
6
"os"
7
"strings"
8
)
9
10
type myElement struct {
11
Name string
12
SurName string
13
Id string
14
}
15
16
var DATA = make(map[string]myElement)
Copied!
我们使用原生的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
12
func DELETE(k string) bool {
13
if LOOKUP(k) != nil {
14
delete(DATA, k)
15
return true
16
}
17
return false
18
}
Copied!
这部分代码实现了命令行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
11
func CHANGE(k string, n myElement) bool {
12
DATA[k] = n
13
return true
14
}
15
16
func PRINT() {
17
for k, v := range DATA {
18
fmt.Printf("key: %s value: %v",k,v)
19
}
20
}
Copied!
该代码段实现了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
8
switch len(tokens) {
9
case 0:
10
continue
11
case 1:
12
tokens = append(tokens,"")
13
tokens = append(tokens,"")
14
tokens = append(tokens,"")
15
tokens = append(tokens,"")
16
case 2:
17
tokens = append(tokens,"")
18
tokens = append(tokens,"")
19
tokens = append(tokens,"")
20
case 3:
21
tokens = append(tokens,"")
22
tokens = append(tokens,"")
23
case 4:
24
tokens = append(tokens,"")
25
26
}
Copied!
该部分代码读取用户的输入。首先,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
21
case "CHANGE":
22
n := myElement{tokens[2],tokens[3],tokens[4]}
23
if !CHANGE(tokens[1],n) {
24
fmt.Println("Update operation failed")
25
}
26
27
default:
28
fmt.Println("Unknown command - please try again!")
29
30
}
31
}
32
}
Copied!
这部分代码处理用户的输入。switch的使用使得程序的逻辑设计看上去十分清晰,能够将程序员从冗余的if...else中拯救出来。
执行keyValue.go得到如下输出:
1
$ go run keyValue.go
2
UNKNOWN
3
Unknown command - please try again!
4
5
ADD 123 1 2 3
6
ADD 234 2 3 4
7
ADD 234
8
Add operation failed
9
ADD 345
10
PRINT
11
key: 123 value: {1 2 3}key: 234 value: {2 3 4}key: 345 value: { }
12
CHANGE 345 3 4 5
13
PRINT
14
key: 345 value: {3 4 5}key: 123 value: {1 2 3}key: 234 value: {2 3 4}
15
DELETE 345
16
PRINT
17
key: 123 value: {1 2 3}key: 234 value: {2 3 4}
18
ADD 567 -5 -6 -7
19
PRINT
20
key: 123 value: {1 2 3}key: 234 value: {2 3 4}key: 567 value: {-5 -6 -7}
21
CHANGE 567
22
PRINT
23
key: 567 value: { }key: 123 value: {1 2 3}key: 234 value: {2 3 4}
24
STOP
Copied!
学习第8章后你将学会如何支持K-V存储的数据持久化功能。然而,在单用户应用使用goroutines和channel是没有任何实际意义的,但是如果你是通过TCP/IP实现K-V存储,那么使用goroutines和channel会帮助你实现多连接、服务多用户功能。你将在第9、10章学习goroutines和channel的知识,并且在12、13章学习网络编程的相关知识,尽情期待吧!
Last modified 2yr ago
Copy link