Go 语言源码分析——map

哈希表用于存储键值对的映射关系,具有O(1)的读写性能。通过哈希函数可以将不同的键映射到不同索引上,当不同的键映射到同一个索引上时,会产生哈希冲突,可通过开放寻址法、链表法来解决哈希冲突,其中Go使用的是链表法。

一、数据结构 

map将键值对存放在桶数组中,每个桶只保存8个键值对,通过键的低8位选择桶,通过键的高8位选择放在桶的哪个位置。如果有超过8个键值对映射到同一个桶,则会放到溢出桶

type hmap struct {
	count     int    // 当前map的元素数量
	flags     uint8
	B         uint8  // 当前持有的buckets数量,因为总是2的倍数,所以存的是对数值
	noverflow uint16 // 溢出的bucket数量
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // 指向bucket的指针
	oldbuckets unsafe.Pointer // 用于扩容时保存之前的bucket,大小是当前bucket的一半
	nevacuate  uintptr        // 迁移进度

	extra *mapextra // 当键值非指针时使用
}

type mapextra struct {
	// 当键值非指针时,溢出桶存放到mapextra结构中,overflow存放buckets中的溢出桶,oldoverflow存放oldbuckets中的溢出桶,nextOverflow预分配溢出桶空间
	overflow    *[]*bmap
	oldoverflow *[]*bmap

	nextOverflow *bmap
}

type bmap struct {
	tophash [bucketCnt]uint8  // bucketCnt=8,一个桶只能放8个键值对,tophash存放了键的哈希的高8位,通过比较不同键的哈希的高 8 位可以减少访问键值对次数以提高性能
}

二、操作

1.创建map

map是通过makemap方法创建的,该方法接受一个要创建的map的类型,要创建的map的大小,以及一个hmap指针,返回一个hmap指针

makemap(t *maptype, hint int, h *hmap) *hmap

首先对要创建的map大小是否溢出进行检查

mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
if overflow || mem > maxAlloc {
	hint = 0
}

若传入的h为空则初始化一个map

if h == nil {
	h = new(hmap)
}
h.hash0 = fastrand()

然后计算合适的桶的数量

B := uint8(0)
for overLoadFactor(hint, B) {
	B++
}
h.B = B

然后对桶进行初始化,其中如果B为0,则桶的初始化延迟到赋值时进行

if h.B != 0 {
	var nextOverflow *bmap
	h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
	if nextOverflow != nil {
		h.extra = new(mapextra)
		h.extra.nextOverflow = nextOverflow
	}
}

return h

其中调用了makeBucketArray计算出要创建的桶的数量并为此分配内存

func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
	base := bucketShift(b)
	nbuckets := base
	// 当桶的数量小于2的4次方时,由于数据量较小,使用溢出桶可能性较低,省去创建过程,否则还需额外创建2的b-4次方个桶
	if b >= 4 {
		nbuckets += bucketShift(b - 4)
		sz := t.bucket.size * nbuckets
		up := roundupsize(sz)
		if up != sz {
			nbuckets = up 
		}
	}

	if dirtyalloc == nil {
        // 分配一个新的数组
		buckets = newarray(t.bucket, int(nbuckets))
	} else {
		// 清理原来的数组
		buckets = dirtyalloc
		size := t.bucket.size * nbuckets
		if t.bucket.ptrdata != 0 {
			memclrHasPointers(buckets, size)
		} else {
			memclrNoHeapPointers(buckets, size)
		}
	}

	if base != nbuckets {
		// 找到溢出桶的位置,因为预先分配的溢出桶的overflow都是nil,若要知道哪个溢出桶是最后一个需要进行标记
		nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))
		last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))
		last.setoverflow(t, (*bmap)(buckets))
	}
	return buckets, nextOverflow
}

2.获取元素

当使用m[key]获取对应的value时,会调用mapaccess1方法,该方法接受一个map的类型,一个hmap的指针,以及key的指针。该方法永远不会返回nil,若该key不存在map时,会返回值的类型对应的空值,因此还可通过返回一个bool值标识该key是否存在mapÿ

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值