本节你将学习使用Go实现K-V存储的简单实现,其背后的思想是易于理解的:尽可能快速地发出请求并给出响应,并将其转化成对应的数据结构。
本节代码将实现K-V存储的四个基本功能:
- 添加新元素
- 基于key删除已有的元素
- 给定key查找对应value
- 修改key对应的value
我们将这四个功能命名为ADD
,DELETE
,LOOKUP
,CHANGE
,完成这四个基本功能,你将会对K-V存储的实现有一个全面的了解。另外,当你输入STOP
时整个程序就会停止,输入PRINT
命令就会打印出当前K-V存储的内容。
本节的keyValue.go
将分为5个代码段解释。
第一部分:
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
是自定义的结构体。
第二部分代码:
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出现)而不是修改对应的值。
第三部分代码:
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存储的内容。
第四部分代码:
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
。
最后一部分代码:
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章
学习网络编程的相关知识,尽情期待吧!