Golang map十问

本文详细介绍了Go语言中map的底层实现、不可比较性、删除不存在key的行为、对不存在key取值的结果、value的寻址限制、struct作为key的注意事项、无序性、多值返回、未初始化map的使用以及并发安全性问题。同时,文章讨论了如何通过读写锁实现线程安全的map,并介绍了sync.map的并发优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、map的底层

底层数据结构:哈希表

2、map是否可比较

不可以,go中的map,slice,函数值都是不可比较的;

其中,map[Key]value中的key必须是可比较的,也就是可以通过 ==或!=操作符进行比较,value的值类型无所谓

3、删除一个不存在的key会panic吗?

不会,相当于delete()没生效

4、对不存在的key取值会发生什么?

对不存在key取值,得到类型零值

5、map中的value是可以被寻址的吗?

不可寻址,对于x=y的赋值操作必须要知道x的地址,所以

type Math struct{
  x,y int
}
var m=map[string]Math{
	"foo":{2,3},
}
func main(){
 m["foo"].x = 5 //报错
}

6、当struct作为map的key时的一个坑

// 当struct作为map的key时,struct对象内部的某个字段发生改变后,查询map时就无法查找原来的值
	m1 :=make(map[Math]string)
	mm:=Math{1,2}
	m1[mm]="haha"
	fmt.Println(m1)
	mm.x = 9
	fmt.Println(m1[mm]) // 没有输出结果

7、map是有序还是无序的

无序,当遍历一个 map 对象的时候,迭代的元素的顺序是不确定的,无法保证两次遍历的顺序是一样的,也不能保证和插入的顺序一致。那怎么办呢?如果我们想要按照 key 的顺序获取 map 的值,需要先取出所有的 key 进行排序,然后按照这个排序的 key 依次获取对应的值

8、map[key]返回的结果可以是一个,也可以是两个值

func main() {
    var m = make(map[string]int)
    m["a"] = 0
    fmt.Printf("a=%d; b=%d\n", m["a"], m["b"])
	// a=0;b=0
    av, aexisted := m["a"]
    bv, bexisted := m["b"]
    fmt.Printf("a=%d, existed: %t; b=%d, existed: %t\n", av, aexisted, bv, bexisted)
}
// a=0, existed: true; b=0, existed: false

9、只用var声明的map可以直接进行操作吗

不可以,和slice、mutex、RWmutex等struct类型不同,map对象必须在使用之前初始化,如果不初始化会出现panic异常

func main() {
    var m map[int]int
    m[100] = 100
}

10、map是并发安全的吗?

不是,go的内建的map对象不是线程(goroutine)安全的,并发读写的时候运行时会有检测,遇到并发问题就会导致panic。sync包的中sync.map是并发安全的

func main() {
    var m = make(map[int]int,10) // 初始化一个map
    go func() {
        for {
            m[1] = 1 //设置key
        }
    }()

    go func() {
        for {
            _ = m[2] //访问这个map
        }
    }()
    select {}
}

改进:使用一个读写锁实现线程安全的map[int]int类型

type RWMap struct { // 一个读写锁保护的线程安全的map
    sync.RWMutex // 读写锁保护下面的map字段
    m map[int]int
}
// 新建一个RWMap
func NewRWMap(n int) *RWMap {
    return &RWMap{
        m: make(map[int]int, n),
    }
}
func (m *RWMap) Get(k int) (int, bool) { //从map中读取一个值
    m.RLock()
    defer m.RUnlock()
    v, existed := m.m[k] // 在锁的保护下从map中读取
    return v, existed
}

func (m *RWMap) Set(k int, v int) { // 设置一个键值对
    m.Lock()              // 锁保护
    defer m.Unlock()
    m.m[k] = v
}

func (m *RWMap) Delete(k int) { //删除一个键
    m.Lock()                   // 锁保护
    defer m.Unlock()
    delete(m.m, k)
}

func (m *RWMap) Len() int { // map的长度
    m.RLock()   // 锁保护
    defer m.RUnlock()
    return len(m.m)
}

func (m *RWMap) Each(f func(k, v int) bool) { // 遍历map
    m.RLock()             //遍历期间一直持有读锁
    defer m.RUnlock()

    for k, v := range m.m {
        if !f(k, v) {
            return
        }
    }
}

更高效的分片加锁并发maphttps://github.com/orcaman/concurrent-map

sync.map的实现

  • 空间换时间,通过两个冗余的数据结构(只读的read字段,可写的dirty),来减少加锁对性能的影响,对只读字段(read)的操作不需要加锁
  • 优先从read字段读取,更新,删除,因为对read字段的读取不需要加锁
  • 动态调整:miss次数多了之后,将dirty数据提升为read,避免总时从dirty中加锁读取
  • double-checking。加锁之后先还要检查read,确定真的不存在才操作dirty字段
  • 延迟删除。删除一个键值只是打标记,只有在提升dirty字段为read字段的时候才清理删除的数据
  • map的数据结构中包含的字段:mutex,read,dirty,misses
  • sync.map拥有的方法:
    • store方法:设置一个键值对
    • load方法:读取一个key对应的值
    • delete方法:删除。先从read操作开始,如果read中不存在,那么就从dirty中寻找,最终,如果存在就删除(将它的值标记为nil),如果项目不为nil或者没有被标记为expunged,那么还可以把它的值返回
    • 其他方法:loadAndDelete,LoadOrStore,Rang,但是没有Len这样的查询map内部元素个数的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值