首先,Go
语言的map
底层是哈希表,而C++
的map
的底层是红黑树,C++
的unordered_map
的底层才是哈希表。所以增删改查的时间复杂度都是O(1)
。当我们使用的时候需要注意以下几点:
map
是引用类型,如果两个map
同时指向一个底层,那么一个map
的变动会影响到另一个map
,这一点需要特殊注意。map
的零值(Zero Value)是nil
,对nil
map
进行任何添加元素的操作都会触发运行时错误(panic
)。因此,使用必须先创建map
,使用make
函数,例如:func f(int n) { m := make([]map[int]int, n) for i := range m { m[i] = make(map[int]int) // m中的所有map在使用前必须初始化 } }
map
的键可以是任何可以用==
或!=
操作符比较的类型,比如字符串、整数、浮点数、复数和布尔类型等。但是,slice
,map
,function
等类型不可以作为map
的键。map
在使用过程中不保证顺序遍历,每次遍历的结果可能会不同,这一点和C++
的unordered_map
相似。map
进行的所有操作,增删改都不是线程安全的,如果在一个goroutine
中修改了map
,同时在另一个goroutine
中读取了map
,可能会触发concurrent map read and map write
的错误。
并发安全:
由上述的第五点可知,Go
语言的map
不是并发安全的。并发情况下,对于map
的读写操作都需要加锁,或者会引起竞争条件甚至导致程序崩溃。为了在并发环境下使用map
,我们可以使用sync
包中的sync.RWMutex
读写锁,或者使用sync.Map
。
比如,我们可以使用读写锁来保证多个goroutine
都可以访问同一个map
:
var m = make(map[string]int)
var mutex = &sync.RWMutex{}
// 基本使用流程与C++和Java中锁的流程类似
func write(key string, value int) {
mutex.Lock()
m[key] = value
mutex.Unlock()
}
func read(key string) (int, bool) {
mutex.Lock()
defer mutex.RUnlock()
value, ok := m[key]
return value, ok
}
再比如,我们也可以使用sync.Map{}
完成并发控制:
func f() {
m := sync.Map{}
list := []string{"A", "B", "C", "D"}
wg := sync.WaitGroup{}
// 添加协程,等待协程完成
for i := 0; i < 20; i++ {
// 启动20组协程
wg.Add(1)
// 添加协程数量,一次只启动一个
go func() {
defer wg.Done() // 协程结束之后 wg - 1
for _, item := range list {
// 存在则 + 1, 不存在则初始化为1
value, ok := m.Load(item)
if !ok {
value, _ = m.LoadOrStore(item, 0)
}
val := value.(int) + 1
m.Store(item, val)
}
}()
}
wg.Wait() // 等待所有协程完成
m.Range(func(k, v any) bool {
fmt.Println(k, v)
return true
})
fmt.Println(m)
}
最后给大家推荐一个LinuxC/C++高级架构系统教程的学习资源与课程,可以帮助你有方向、更细致地学习C/C++后端开发,具体内容请见 https://xxetb.xetslk.com/s/1o04uB