Golang-MAP源码学习、扩容机制等

前言

map是golang中常用的一个基本数据结构,继上篇的channel源码学习之后,本文学习map的源码相关。

基础知识点

此部分参考自 https://www.zhihu.com/tardis/zm/art/423008350?source_id=1003

map的用法

通过make初始化分配容量,通过m[key] = value进行设置。

map初始化时为什么要关注容量

容量不够时候就需要涉及到扩容,所以一次分配够了,减少扩容次数,提高性能。

demo:

提升了两倍多的性能。

遍历时的无序性

具体自己先前文章中写过

对于map的操作

具体来说,就是边遍历边新增/删除。删除了的不可能再遍历到,新增的可能再遍历到(不一定)。
原因:map内部实现是一个链式hash表,为了保证无顺序,初始化时会随机一个遍历开始的位置,所以新增的元素被遍历到就变的不确定了,同样删除也是一个道理,但是删除元素后边就不会出现,所以一定不会被遍历到。

另一个点是,他随机并不是完全等概率的随机,第一个元素被选中的概率更高一些。

nil-map的写入操作会引发panic

扩容是在已申请的容量不够用时候,会触发。

但是直接0个容量,没有初始化。那就直接panic了。

map并发安全吗?不安全!

可以使用REMutex或者sync.Map去实现并发安全的map。

底层结构-源码

源码位于src\runtime\map.go中

源码

map底层是一个hmap,其中内容:

go 复制代码
// A header for a Go map.
type hmap struct {
	// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
	// Make sure this stays in sync with the compiler's definition.
	count     int // # live cells == size of map.  Must be first (used by len() builtin)
	flags     uint8
	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
	oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // optional fields
}

含义:

count:map的当前长度,用len(m)输出的就是。

(可以理解为喝slice那边一样,一个len 一个cap)

B:和桶的多少相关,2^B = 桶的个数。

noverflow:表示近似溢出桶的数量。

hash0:哈希因子。

go 复制代码
	buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
	oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing

之后这两个对应的是hash桶,其中一个是扩容时候所用的。

提高效率的几处精妙设计

key hash值的后B位作为桶index查找桶

key hash值的前8位作为桶内结构体的三个数组(tophash,key,value)的index

桶结构体的tophash复用,既作为tophash使用,也作为标志位使用

灵活的扩容机制

工作流程-源码

创建

通过makemap函数进行创建。

go 复制代码
// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap {
	mem, overflow := math.MulUintptr(uintptr(hint), t.Bucket.Size_)
	if overflow || mem > maxAlloc {
		hint = 0
	}

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

	// Find the size parameter B which will hold the requested # of elements.
	// For hint < 0 overLoadFactor returns false since hint < bucketCnt.
	B := uint8(0)
	for overLoadFactor(hint, B) {
		B++
	}
	h.B = B

	// allocate initial hash table
	// if B == 0, the buckets field is allocated lazily later (in mapassign)
	// If hint is large zeroing this memory could take a while.
	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
}

扩容

扩容主要分为两种:

1、装载因子已经超过 6.5------放不下了;

2、哈希使用了太多溢出桶------链表太长 太乱了;

放不下了扩容

溢出桶过多整理

sameSizeGrow 扩容,其本质并不是扩容,而是重新整理,减少链表。

放不下了扩容

biggerSizeGrow扩容。当大于6.5时候,B+1,即翻倍桶的个数,之后进行迁移。

go 复制代码
func hashGrow(t *maptype, h *hmap) {
	// If we've hit the load factor, get bigger.
	// Otherwise, there are too many overflow buckets,
	// so keep the same number of buckets and "grow" laterally.
	bigger := uint8(1)
	if !overLoadFactor(h.count+1, h.B) {
		bigger = 0
		h.flags |= sameSizeGrow
	}
	oldbuckets := h.buckets
	newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)

	flags := h.flags &^ (iterator | oldIterator)
	if h.flags&iterator != 0 {
		flags |= oldIterator
	}
	// commit the grow (atomic wrt gc)
	h.B += bigger
	h.flags = flags
	h.oldbuckets = oldbuckets
	h.buckets = newbuckets
	h.nevacuate = 0
	h.noverflow = 0

	if h.extra != nil && h.extra.overflow != nil {
		// Promote current overflow buckets to the old generation.
		if h.extra.oldoverflow != nil {
			throw("oldoverflow is not nil")
		}
		h.extra.oldoverflow = h.extra.overflow
		h.extra.overflow = nil
	}
	if nextOverflow != nil {
		if h.extra == nil {
			h.extra = new(mapextra)
		}
		h.extra.nextOverflow = nextOverflow
	}

	// the actual copying of the hash table data is done incrementally
	// by growWork() and evacuate().
}
相关推荐
小电玩13 分钟前
JAVA SE8
java·开发语言
秋秋秋叶15 分钟前
Python学习——【3.1】函数
python·学习
小丁爱养花39 分钟前
记忆化搜索专题——算法简介&力扣实战应用
java·开发语言·算法·leetcode·深度优先
爱上语文1 小时前
Springboot三层架构
java·开发语言·spring boot·spring·架构
Crossoads1 小时前
【数据结构】排序算法---快速排序
c语言·开发语言·数据结构·算法·排序算法
6230_1 小时前
git使用“保姆级”教程2——初始化及工作机制解释
开发语言·前端·笔记·git·html·学习方法·改行学it
挽月0011 小时前
C++单例模式
开发语言·c++·单例模式
Pandaconda1 小时前
【计算机网络 - 基础问题】每日 3 题(十)
开发语言·经验分享·笔记·后端·计算机网络·面试·职场和发展
loveLifeLoveCoding1 小时前
Java List sort() 排序
java·开发语言
AngeliaXue1 小时前
Java集合(List篇)
java·开发语言·list·集合