简介
哈希表是计算机领域应用最广泛的数据结构之一。通常情况下,哈希表都会提供快速增/删/改/查数据的操作。go语言中也对哈希表做了支持,也就是map结构。
声明和初始化
map的声明方式如下,其中keyType应该是可比较的数据,而valueType可以是任何类型,甚至可以是一个map
map[keyType]valueType
接下来,声明一个key值为string,value值为int的map —> m
var m map[string]int
map是一个引用类型,如果只声明而没有初始化,则其值为nil,表现为一个empty map,但是需要注意的是,不能直接使用这个map来执行以下赋值操作
m["test"] = 5 //直接使用这个赋值会报错 --> panic: assignment to entry in nil map
赋值操作前,需要初始化map,如下
m = make(map[string]int)
make函数会返回一个map对象并赋值给m,关于map的具体实现本篇不多做探讨,仅讨论map的具体用法
当然,如果不使用make函数,也可以使用如下方式初始化map
m = map[string]int{}
如果需要添加初始值的话,也可以在大括号{}中以key : value的形式添加初始值
m = map[string]int{
"liming": 88,
"lihua": 99,
"xiaohong": 100,
}
map的使用
map的赋值或修改操作如下
如果map中不包含 key 值为 “liming” 的数据,则会新增加一个 key 值为 ”liming“ value 值为 87 的 entry
如果map中包含 key 值为 “liming” 的数据,则会将 key 值为 “liming” 的数据 value 值修改为 87
m["liming"] = 87 //map --> map[lihua:99 liming:88 xiaohong:100]
获取map中的value值操作如下
如果map中不包含key值为 ”liming“ 的数据,则会获取一个默认值 0 (这里获取默认值为0是因为value类型为 int 如果是其他类型则获取其他类型的默认值)
score := m["liming"]
删除map中的元素
执行delete后,将会删除map中key值为“wang”的数据,这里需要了解的是,如果map中不存在这个key值,delete函数不会做任何操作
delete(m, "wang")
获取map的长度
n := len(m) // n == 3
map值的获取实际上返回的是两个值,如下
score获取key值“liming” 在map中对应的value类型的值,若key值不存在,则score为0(int默认值),ok则是一个bool类型,用来判断key值是否存在于map,如果存在则 ok 为true,如果不存在则 ok 为false
score, ok := m["liming"] //score == 87, ok == true
如果仅需判断key值是否存在,也可以使用符号 ‘_’ 来代替score变量,这样就可以忽略对value的使用
_, ok := m["liming"]
使用for循环遍历map
for key, value := range m {
fmt.Println("key:", key, "value:", value)
}
//key: xiaohong value: 100
//key: liming value: 87
//key: lihua value: 99
默认值的更多用法
循环链表的检测
map中获取不存在key的value值,会返回对应类型的默认值,利用这一特性,可以用来检测循环链表是否遍历完成
以下程序中,如果visited[n] 中的n不存在于map,则会返回false,遍历过这个值后,将值存入map,后续visited[n]就会返回true,循环下来到起始位置,则visited[n]必定返回true,遍历到此结束
type Node struct {
Next *Node
Value interface{}
}
var first *Node
visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
if visited[n] {
fmt.Println("cycle detected")
break
}
visited[n] = true
fmt.Println(n.Value)
}
map中slices的默认值
在经过make函数初始化map后,以下程序默认的value值会是一个空的slices(这个也是nil值,但是支持slices的append等操作,slices不像map需要初始化,只需要声明即可使用),用来存储对应的数据
type Person struct {
Name string
Likes []string
}
var people []*Person
likes := make(map[string][]*Person)
for _, p := range people {
for _, l := range p.Likes {
likes[l] = append(likes[l], p)
}
}
map中的key
前面说道,只要是可比的类型,都可以当做map的key值,在go中,可比较类型主要有 boolean, numeric, string, pointer, channel,interface types, 另外 structs 或者 arrays 如果只包含上述类型的话,也可以当做key值。但slices,map以及function就不可以作为key使用
map的并发
map并不是线程安全的实现,原因在官方文档解释如下
### Why are map operations not defined to be atomic?
After long discussion it was decided that the typical use of maps did not require safe access from multiple goroutines, and in those cases where it did, the map was probably part of some larger data structure or computation that was already synchronized. Therefore requiring that all map operations grab a mutex would slow down most programs and add safety to few. This was not an easy decision, however, since it means uncontrolled map access can crash the program.
The language does not preclude atomic map updates. When required, such as when hosting an untrusted program, the implementation could interlock map access.
Map access is unsafe only when updates are occurring. As long as all goroutines are only reading—looking up elements in the map, including iterating through it using a `for` `range` loop—and not changing the map by assigning to elements or doing deletions, it is safe for them to access the map concurrently without synchronization.
As an aid to correct map use, some implementations of the language contain a special check that automatically reports at run time when a map is modified unsafely by concurrent execution.
大致意思为
- map可能应用于已经实现了并发的程序中,另外如果实现map的线程安全,将会极大拖慢程序效率,并且只得到较小的安全性提升
- map的并发不安全主要体现在更新操作中,但是map一般的操作都是读取数据,这类操作在并发中是安全的
- go的一些特殊实现会有安全检查,当map执行了并发不安全操作时,会自动报错
如果平时使用map时候必须实现线程安全的话,可以使用 sync.RWMutex
声明一个匿名struct,其中包含内置的 sync.RWMutex以及一个map
var counter = struct{
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}
读操作
counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)
写操作
counter.Lock()
counter.m["some_key"]++
counter.Unlock()
map的遍历
在使用map的过程中,应该不难发现,map遍历出来的数据并不是每次都是相同的顺序,如果需要指定顺序的话,就需要将map的key值用另一个可以支持排序的数据结构保存起来,这时,想要顺序遍历map就可以通过顺序获取key,再通过指定key来获取map中的值
import "sort"
var m map[int]string
var keys []int
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
fmt.Println("Key:", k, "Value:", m[k])
}