集合map
算法贴士
需要使用任意类型的关联,就需要用到集合,比如学号和名字。Go语言提供了映射关系容器是map,内部使用散列表(hash)实现。
大多数语言中映射关系容器使用两种算法:散列表和平衡树。
散列表可以简单的描述为一个数组,数组的每个元素都是列表,根据散列函数获得每个元素的特征值,将特征值作为映射的键。如果特征值重复,表示元素发生了碰撞,需要尽量避免碰撞,这样就需要多容器扩容,每次扩容,元素都需要重新放入,较为耗时。
平衡树类似于有父子关系的一棵数据数,每个元素在放入树时,都要与一些节点进行比较。
map的创建
Go内置了map类型,map是一个无序键值对集合(也有一些书籍翻译为字典)。
普通创建: 1
2
3//声明一个map类型,[]内的类型指任意可以进行比较的类型 int指值类型
m := map[string]int{"a":1,"b":2}
fmt.Print(m["a"])
make方式创建map: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15type Person struct{
ID string
Name string
}
func main() {
var m map[string] Person
m = make(map[string] Person)
m["123"] = Person{"123","Tom"}
p,isFind := m["123"]
fmt.Println(isFind) //true
fmt.Println(p) //{123 Tom}
}
注意:key 可以是什么类型? golang 中的 map,的 key 可以是很多种类型,比如 bool, 数字,string, 指针, channel , 还可以是只 包含前面几个类型的 接口, 结构体, 数组。
通常 key 为 int 、string。
注意: slice, map 还有 function 不可以,因为他们不能使用 == 来判断
map的使用
map的读取和设置也类似slice一样,通过key来操作,只是slice的index只能是`int`类型,而map多了很多类型,可以是int,可以是string及所有完全定义了==与!=操作的类型。 1
2
3
4
5
6
7
8
9
10// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
var numbers map[string]int
// 另一种map的声明方式
numbers = make(map[string]int)
numbers["one"] = 1 //赋值
numbers["ten"] = 10 //赋值
numbers["three"] = 3
fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
// 打印出来如:第三个数字是: 3
注意: map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取; map的长度是不固定的,也就是和slice一样,也是一种引用类型
内置的len函数同样适用于map,返回map拥有的key的数量 map的值可以很方便的修改,通过numbers["one"]=11可以很容易的把key为one的字典值改为11 map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
删除元素
1 | // 初始化一个字典 |
注意:go没有提供清空元素的方法,可以重新make一个新的map,不用担心垃圾回收的效率,因为go中并行垃圾回收效率比写一个清空函数高效很多。
sync.Mpa
Go内置的map只读是线程安全的,读写是线程不安全的,并发安全的map可以使用标准包sync中的map。
演示并发读写map的问题: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package main
func main() {
m := make(map[int]int)
go func() {
for { //无限写入
m[1] = 1
}
}()
go func() {
for { //无限读取
_ = m[1]
}
}()
for {} //无限循环,让并发程序在后台执行
}
错误提示:fatal error: concurrent map read and map write
,即出现了并发读写,因为用两个并发程序不断的对map进行读和写,产生了竞态问题。map内部会对这种错误进行检查并提前发现。
需要并发读写时,一般都是加锁,但是这样做性能不高,在go1.9版本中提供了更高效并发安全的sync.Map。
sync.Map的特点: - 无须初始化,直接声明即可 - sync.Map不能使用map的方式进行取值和设值操作,而是使用sync.Map的方法进行调用。Store表示存储,Load表示获取,Delete表示删除。 - 使用Range配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,需要继续迭代时,返回true,终止迭代返回false。
1 | package main |
注意:map没有提供获取map数量的方法,可以在遍历时手动计算。sync.Map为了并发安全。损失了一定的性能。