Go 语言中的 Map
本文介绍一种特殊的数据结构。它是一种元素对的无序集合,每一个索引(key)对应一个值(value),这种数据结构在 Go 语言中被称之为 map。map 是一种能够通过索引(key)迅速找到值(value)的数据结构,所以也被称为字典。在 Go 语言中因为线程安全问题,一共实现了两种类型的 map,接下来我们每种都了解一下。
Tips:线程的知识会在Go语言的多线程中讲解。
1. 无锁的map
这种类型的 map 是线程不安全的 map,多个线程同时访问这个类型的 map 的同一个变量时,会有读写冲突,会导致系统奔溃。所以一般在单线程程序中使用的较多。
1.1 map 的创建
map 的底层结构也是一个指针,所以和变量不同,并不是声明后立刻能够使用。和切片相同,需要使用make()函数进行初始化。在初始化之前为空,没有零值。
代码示例:
package main
import (
"fmt"
)
func main() {
var m map[string]string
fmt.Println(m == nil)
m = make(map[string]string)
fmt.Println(m == nil)
}
- 第 8 行:声明一个 key 为 string 类型,value 为 string 类型的 map 变量;
- 第 9 行:此时 m 未初始化,值为 nil;
- 第 10 行:初始化 m。
- 第 11 行:此时 m 是一个没有存放数据的 map,值不为 nil。
执行结果:
1.2 map 的赋值
map 的赋值有两种方式:
- 使用
:=
使map在定义的时候直接赋值; - 使用
map[key]=value
的形式对map进行赋值。
在明确知道 map 的值的时候就可以使用第一种方式进行赋值,比如说在建立中英文对应关系的时候。在未知 map 的取值时,一般建议使用后者进行赋值。
代码示例:
package main
import "fmt"
func main() {
m1 := map[string]string{"Apple": "苹果", "Orange": "橘子", "Banana": "香蕉"}
fmt.Println(m1["Apple"])
m2 := make(map[string]string)
m2["Apple"] = "苹果"
m2["Orange"] = "橘子"
m2["Banana"] = "香蕉"
fmt.Println(m2["Apple"])
}
- 第 6 行:在 m1 被定义的时候直接赋值;
- 第 7 行:输出 m 1中 key 为 “Apple” 时对应的值;
- 第 8 行:使用
:=
进行免声明 make; - 第 9~11 行:对 m2 进行赋值;
- 第 12 行:输出 m2 中 key 为 “Apple” 时对应的值。
执行结果:
1.3 map 的遍历
map 是字典结构,如果不清楚所有 key 的值,是无法对 map 进行遍历的,所以 Go 语言中使用了一个叫做range的关键字,配合for循环结构来对map结构进行遍历。
Tips:range同时也可以用来遍历数组和切片,数组和切片在range中可以看为
map[int]数据类型
结构,遍历和用法和map一致。
代码示例:
package main
import "fmt"
func main() {
m := map[string]string{"Apple": "苹果", "Orange": "橘子", "Banana": "香蕉"}
for k, v := range m {
fmt.Println("key:", k, ", value:", v)
}
}
- 第 7 行:使用 range 关键字,每次 for 循环都会取出一个不重复的 key 和 value,赋值给 k 和 v,直至循环结束。
Tips:map 是无序的,所以每次输出的顺序可能会不一样。
执行结果:
1.4 map 的删除
map 在普通的用法中是无法移除只可以增加 key 和 value 的,所以 Go 语言中使用了一个内置函数delete(map,key)
来移除 map 中的 key 和 value。
代码示例:
package main
import "fmt"
func main() {
m := map[string]string{"Apple": "苹果", "Orange": "橘子", "Banana": "香蕉"}
fmt.Println(m)
delete(m, "Apple")
fmt.Println(m)
}
- 第8行:删除 m 中的 “Apple” 和其对应的 value。
执行结果:
2. 自带锁的 sync.Map
这种类型的 map 是线程安全的 map,多个线程同时访问这个类型的 map 的同一个变量时,不会有读写冲突,因为它自带原子锁,保障了多线程的数据安全。
2.1 sync.Map 的创建
这种类型的 map 创建不需要make,直接声明就可以使用,而且不需要声明 map 的 key 和 value 的类型。因为它底层的实现并不是指针,是一种多个变量的聚合类型,叫做结构体。
Tips:结构体的概念会在Go语言的结构体中讲解
代码示例:
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
fmt.Println(m)
}
- 第 9 行:声明一个 sync.Map。
- 第 10 行:输出 m 的零值。
执行结果:
2.2 sync.Map 的操作
这个类型关于 map 的所有操作都是使用它自带的方法来实现的。包括range。
代码示例:
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
m.Store("Apple", "苹果")
m.Store("Orange", "橘子")
m.Store("Banana", "香蕉")
tmp, exist := m.Load("Orange")
fmt.Println(tmp, exist)
m.Delete("Banana")
m.Range(func(k, v interface{}) bool {
fmt.Println("key:", k, ", value:", v)
return true
})
}
- 第 11~13 行:使用 Store 方法给 m 赋值;
- 第 14 行:使用 Load 取出 “Orange” 对应的值,如果不存在 “Orange” 这个 key,exist 的值为 false;
- 第 17 行:删除 m 中的 “Banana” 和其对应的 value;
- 第 19 行:使用 Range 方法遍历 m。
执行结果:
3. 小结
本文主要讲解了两个 map 数据类型,两种在功能上区别并不大,主要是在应用上。map[数据类型]数据类型
一般使用在单线程场景,多线程场景使用sync.Map
。在赋值上map[数据类型]数据类型
可以赋初值,且需要指定数据类型。sync.Map
无法赋初值,无需指定数据类型。
- 还没有人评论,欢迎说说您的想法!