【GO】map 实现原理

本文基于小徐先生的编程世界学习map

1.基本用法

1.核心特征

(1)存储基于 key-value 对映射的模式;
(2)基于 key 维度实现存储数据的去重;
(3)读、写、删操作控制,时间复杂度 O(1).

2.初始化方法

myMap1 := make(map[int]int,2)
myMap2 := make(map[int]int)
myMap3 :=map[int]int{
   
   
  1:2,
  3:4,
}

3.key 的类型要求

map 中,key 的数据类型必须为可比较的类型,slice、map、func不可比较

4.读

读 map 分为下面两种方式:

v1 := myMap[10]

第一种方式是直接读,倘若 key 存在,则获取到对应的 val,倘若 key 不存在或者 map 未初始化,会返回 val 类型的零值作为兜底.

v2,ok := myMap[10]

第二种方式是读的同时添加一个 bool 类型的 flag 标识是否读取成功. 倘若 ok == false,说明读取失败, key 不存在,或者 map 未初始化.

此处同一种语法能够实现不同返回值类型的适配,是由于代码在汇编时,会根据返回参数类型的区别,映射到不同的实现方法.

5.写

myMap[5] = 6

写操作的语法如上. 须注意的一点是,倘若 map 未初始化,直接执行写操作会导致 panic

6.删除

delete(myMap,5)

执行 delete 方法时,倘若 key 存在,则会从 map 中将对应的 key-value 对删除;倘若 key 不存在或 map 未初始化,则方法直接结束,不会产生显式提示.

7. 遍历

遍历分为下面两种方式:

for k,v := range myMap{
   
   
  // ...
}

基于 k,v 依次承接 map 中的 key-value 对;

for k := range myMap{
   
   
  // ...
}

基于 k 依次承接 map 中的 key,不关注 val 的取值.
需要注意的是,在执行 map 遍历操作时,获取的 key-value 对并没有一个固定的顺序,因此前后两次遍历顺序可能存在差异.

8.并发冲突

map 不是并发安全的数据结构,倘若存在并发读写行为,会抛出 fatal error.
具体规则是:
(1)并发读没有问题;
(2)并发读写中的“写”是广义上的,包含写入、更新、删除等操作;
(3)读的时候发现其他 goroutine 在并发写,抛出 fatal error;
(4)写的时候发现其他 goroutine 在并发写,抛出 fatal error.

并发安全的 map 通常可以使用以下几种方法:

  1. sync.Map:Go 语言标准库提供的并发安全的 map 实现,适用于读多写少的场景。
  2. 使用互斥锁(sync.Mutex):手动在对 map 进行读写操作时加锁。
  3. 使用读写锁(sync.RWMutex):在读多写少的场景下,使用读写锁可以提高并发性能。

2.核心原理

map 又称为 hash map,在算法上基于== hash表== 实现 key 的映射和寻址;在数据结构上基于桶数组实现 key-value 对的存储.
hash是一种将任意长度的输入压缩到某一固定长度的输出摘要的过程,由于这种转换属于压缩映射,输入空间远大于输出空间,因此不同输入可能会映射成相同的输出结果.
hash的特点:
(1)hash 的可重入性:相同的 key,必然产生相同的 hash 值;
(2)hash 的离散性:只要两个 key 不相同,不论其相似度的高低,产生的 hash 值会在整个输出域内均匀地离散化;
(3)hash 的单向性:企图通过 hash 值反向映射回 key 是无迹可寻的.
(4)hash 冲突:由于输入域(key)无穷大,输出域(hash 值)有限,因此必然存在不同 key 映射到相同 hash 值的情况,称之为 hash 冲突.

3.数据结构

hmap

type hmap struct {
   
   
    count     int     //当前保存元素个数
    flags     uint8
    B         uint8     //bucket数组大小->2^B
    noverflow uint16 //map 中溢出桶的数量;
    hash0     uint32 //hash 随机因子,生成 key 的 hash 值时会使用到
    buckets    unsafe.Pointer //bucket数组,长度为2^B
    oldbuckets unsafe.Pointer //老旧的bucket数组,在扩容时出现
    nevacuate  uintptr     //  扩容时的进度标识,index 小于 nevacuate 的桶都已经由老桶转移到新桶中;
    extra *mapextra //预申请的溢出桶.
}

bmap

bucket数据结构:
const bucketCnt = 8
type bmap struct {
   
   
    tophash [bucketCnt]uint8
    data []byte//k0/..../k7/val1/.../val7每个bucket可以存储8个kv对
    overflow *bmap
}
  • tophash:长度为8的整形数组,存储hash值的高位
  • data:存放kv数据
  • overflow:指向下一个bucket将所有冲突的键连接

在代码层面只展示了 tophash 部分,但由于 tophash、key 和 val 的数据长度固定,因此可以通过内存地址偏移的方式寻找到后续的 key 数组、val 数组以及溢出桶指针

4.构造方法

1.makemap

func makemap(t *maptype, hint int, h *hmap) *hmap {
   
   
    //(1)hint 为 map 拟分配的容量;在分配前,会提前对拟分配的内存大小进行判断,
    mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
    //倘若超限,会将 hint 置为零;
    if overflow || mem > maxAlloc {
   
   
        hint = 0
    }

//(2)通过 new 方法初始化 hmap;
    if h == nil {
   
   
        h = new(hmap)
    }
  //(3)调用 fastrand,构造 hash 因子:hmap.hash0;
    h.hash0 = fastrand()


//致上基于 log2(B) >= hint 的思路,计算桶数组的容量 B;
    B := uint8(0)
    for overLoadFactor(hint, B) {
   
   
        B++
    }
    h.B = B

//(5)调用 makeBucketArray 方法,初始化桶数组 hmap.buckets;
    if h.B != 0 {
   
   
        var nextOverflow *bmap
        h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)6)倘若 map 容量较大,会提前申请一批溢出桶 hmap.extra.
        if nextOverflow != nil {
   
   
            h.extra = new(mapextra)
            h.extra.nextOverflow = nextOverflow
        }
    }


    return 
    }

2.overLoadFactor

(1)倘若 map 预分配容量小于等于 8,B 取 0,桶的个数为 1;
(2)保证 map 预分配容量小于等于桶数组长度 * 6.5.

const loadFactorNum = 13
const loadFactorDen = 2
const goarch.PtrSize = 8
const bucketCnt = 8


func overLoadFactor(count int, B uint8) bool {
   
   
    return count > bucketCnt && uintptr(count) > 13*(bucketShift(B)/2)
}


func bucketShift(b uint8) uintptr {
   
   
    
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值