map
# 定义
散列表是设计精妙、用途广泛的数据结构之一。它是一个拥有键值对元素的无序集合。在这个集合中,键的值是唯一的,键对应的值可以通过获取见获取、更新或移除。无论这个散列表有多大,这些操作基本上是通过常量时间的键比较就可以完成。 在Go语言中,map是散列表的引用,map的类型是map[k]v,其中k和v是字典的键和值对应的数据类型。map中所有的键都拥有相同的数据类型,同时所有的值也都拥有相同的数据类型,但是键的类型和值的类型不一定相同。键的类型k,必须是可以通过操作符==来进行比较的数据类型,所以map可以检测某一个键是否已经存在。 内置函数make可以用来创建一个map:
ages := make(map[string]int) //创建一个从string到int的map
也可以使用map的字面量来新建一个带初始化键值对元素的字典:
ages := map[string]int{
"alice": 31,
"charlie": 34,
}
这个等价于:
ages := make(map[string]int)
ages["alice"] = 31
ages["charlie"] = 34
因此,新的空map的另外一种表达式是:map[string]int{}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 删除数据
可以使用内置函数delete来从字典中根据键移除一个元素,即便传给delete的键在map中并不存在,delete函数的执行也不会失败,更不会抛出运行时的异常。
delete(ages, "alice")
# 遍历map中的键值数据
在Go中,遍历map的键值对只有一种方法,那就是像对待切片那样通过for range语句对map进行遍历。
# map 变量的传递开销
和切片类型一样,map也是引用类型。这就意味着map类型变量作为参数被传递给函数或方法的时候,实际上传递的只是一个“描述符”,而不是整个map的数据拷贝,所以这个传递的开销是固定的,而且也很小。
# 总结
- 不要依赖map的元素遍历顺序
- map 不是线程安全的,不支持并发读写
- 不要尝试获取map中的元素(value)的地址
# 实现并发读写map
避免map并发读写panic的方式之一就是加锁,考虑到读写性能,可以使用读写锁提供性能。
type RWMap struct {
sync.RWMutex
m map[int]int
}
func NewRWMap(n int) *RWMap {
return &RWMap{
m: make(map[int]int, n),
}
}
func (m *RWMap) GET(k int) (int,bool) {
m.RLock()
defer m.RUnlock()
v, existed := m.m[k]
return v, existed
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 分片加锁:更高效的并发map
在并发编程中,一条原则就是尽量减少锁的使用。但是对于Go开发的应用程序来说,并发是常用的一个特性,在这种情况下,我们能做的就是,尽量减少锁的粒度和锁的持有时间。 减少锁的粒度常用的方法就是分片(Shard),将一把锁分成几把锁,每个锁控制一个分片。Go 比较知名的分片并发 map 的实现是orcaman/concurrent-map[https://github.com/elliotchance/orderedmap]。
// Create a new map.
m := cmap.New[string]()
// Sets item within map, sets "bar" under key "foo"
m.Set("foo", "bar")
// Retrieve item from map.
bar, ok := m.Get("foo")
// Removes item under key "foo"
m.Remove("foo")
2
3
4
5
6
7
8
9
10
11