zoukankan      html  css  js  c++  java
  • map

    map

    由<key,value>对组成的抽象数据结构,并且同一个key只出现一次

    实现对比

    方法 hash 搜索树
    复杂度 O(1) O(logN)
    顺序 乱序 有序
    痛点 碰撞问题 平衡问题

    源码位置

    //main.go
    package main
    
    func main(){
    	m := make(map[int]int)
    	m[2] = 2
    }
    
    

    上面代码通过以下指令查看汇编语句
    go tool compile -S main.go

    map源码位置 src/runtime/hashmap.go (go1.9.2版本)

    源码分析

    数据结构

    type hmap struct {
    	//计数,map包含key个数
    	count     int  
    	//标志位,包含是否写、是否遍历,老迭代器还是新迭代器遍历以及是否同大小增长
    	flags     uint8
    	//map里桶的个数的指数
    	B         uint8  
    	//溢出桶的个数(数值过大是会随机计数大致统计)
    	noverflow uint16 
    	//key的hash种子
    	hash0     uint32 
    
    	//指向当前桶数组的指针
    	buckets    unsafe.Pointer 
    	//指向老桶数组的指针
    	oldbuckets unsafe.Pointer 
    	//已迁移的地址,改地址以前的桶都已经迁移(扩容相关)
    	nevacuate  uintptr        
    
    	//特殊用途
    	extra *mapextra 
    }
    
    //当map的键和值都不是指针并且类型为内置类型时,会将桶标记为不包含指针类型,从而减少GC扫描
    //但是桶的结构体中包含overflow指针,因此上述map会将桶的overflow指针放在如下结构体中
    type mapextra struct {
    	//大小为2的数组,值为指向数组的指针
    	//0号指向当前桶数组的地址
    	//1号指向老桶数组的地址
    	overflow [2]*[]*bmap
    	//指向预分配的桶的地址,具体功用还未理解,带填坑
    	nextOverflow *bmap
    }
    
    //具体桶的结构体
    type bmap struct {
    	//大小为8的数组,作用是快速索引key以及记录迁移状态
    	topbits [8]uint8
    	//键值存储位置
    	keys [8]keytype
    	values [8]valuetype
    	//对其字节
    	pad uintptr
    	//溢出桶指针
    	overflow uintptr
    }
    //bmap的key和value都是大小为8的数组,并且先key后value的排列
    //目的是减少因为字节对齐产生的pad
    
    

    hmap结构体示例图

    map创建

    func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
    	//key的合法判断、大小判断、设定的常量判断等操作
    	...
    	// B初始值为0 
    	B := uint8(0)
    	//hint大于8并且hint/2^B超过转载因子则增加B
    	for ; overLoadFactor(hint, B); B++ {
    	}
    
    	//桶地址赋值,如果B=0,则会在mapassign阶段分配一个桶
    	buckets := bucket
    	var extra *mapextra
    	if B != 0 {
    		var nextOverflow *bmap
    		//根据B大小分配桶数组的地址空间,以及预分配一些桶的空间
    		buckets, nextOverflow = makeBucketArray(t, B)
    		if nextOverflow != nil {
    			extra = new(mapextra)
    			extra.nextOverflow = nextOverflow
    		}
    	}
    
    	// 初始化hmap的其他数值 
    	if h == nil {
    		h = (*hmap)(newobject(t.hmap))
    	}
    	h.count = 0
    	h.B = B
    	h.extra = extra
    	h.flags = 0
    	h.hash0 = fastrand()
    	h.buckets = buckets
    	h.oldbuckets = nil
    	h.nevacuate = 0
    	h.noverflow = 0
    
    	return h
    }
    

    map读取

    map的读取函数很多但核心都一样,下面重点分析mapaccess2函数

    map读取过程示例图

    func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool) {
    	//不知道干啥的,好像已经不适用了
    	...
    
    	//map为nil或者大小等于0则返回value类型的0值
    	if h == nil || h.count == 0 {
    		return unsafe.Pointer(&zeroVal[0]), false
    	}
    	//标志位判断是否有协程在写map,有的话抛错误
    	if h.flags&hashWriting != 0 {
    		throw("concurrent map read and map write")
    	}
    	//拿到key的hash函数
    	alg := t.key.alg
    	//通过map的hash种子和key的hash函数算出哈希值
    	hash := alg.hash(key, uintptr(h.hash0))
    	//获取桶的掩码值,即B=5则m=11111
    	m := uintptr(1)<<h.B - 1
    	//通过掩码m获取hash值最后B位数值,得到key所在的桶数组的下标地址
    	b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + (hash&m)*uintptr(t.bucketsize)))
    	//查看是否有老桶数组,有的话表示map正在扩容
    	if c := h.oldbuckets; c != nil {
    		//如果不是等大小扩容,则说明是真正的增加桶个数的扩容
    		//通常是翻倍增加,因此m右移一位即,老桶的掩码值
    		if !h.sameSizeGrow() {
    			m >>= 1
    		}
    		//通过老桶数组的掩码值计算老桶数组中key对应的桶地址
    		oldb := (*bmap)(unsafe.Pointer(uintptr(c) + (hash&m)*uintptr(t.bucketsize)))
    		//判断是否已经搬迁,如果没有搬迁,则用老桶地址来寻找
    		//怎么判断是否搬迁?看桶的第一个tophash是否在1-3之间,1-3为桶的特殊标记
    		//一次搬迁一个桶及其溢出桶,只有搬迁后才会设置特殊标记
    		if !evacuated(oldb) {
    			b = oldb
    		}
    	}
    	//取hash值前8位作为tophash值和桶的tonghash数组对比进行快速定位查找
    	top := uint8(hash >> (sys.PtrSize*8 - 8))
    	//top取值小于4的,需要加4,因为0-3是tophash的特殊标记
    	if top < minTopHash {
    		top += minTopHash
    	}
    	for {
    		//遍历桶的8个位置
    		for i := uintptr(0); i < bucketCnt; i++ {
    			//通过tophash快速判断
    			if b.tophash[i] != top {
    				continue
    			}
    			//tophash值相同的,则把key拿出来
    			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
    			//如果key存的是指针 
    			if t.indirectkey {
    				//取出值来
    				k = *((*unsafe.Pointer)(k))
    			}
    			//计算key和k的值是否相等,tophash一样的不一定key一样哈
    			if alg.equal(key, k) {
    				//一样的话,拿对应的值
    				v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
    				//值存的是指针,则通过指针获取正在的值
    				if t.indirectvalue {
    					v = *((*unsafe.Pointer)(v))
    				}
    				return v, true
    			}
    		}
    		//8个都没有则看是否有溢出桶
    		b = b.overflow(t)
    		if b == nil {
    			//没有溢出桶,返回value类型的0值
    			return unsafe.Pointer(&zeroVal[0]), false
    		}
    	}
    }
    

    map赋值

    func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    	//map为nil 抛panic
    	if h == nil {
    		panic(plainError("assignment to entry in nil map"))
    	}
    	//好像是废弃的代码
    	...
    
    	//判断是否有写的协程在操作
    	if h.flags&hashWriting != 0 {
    		throw("concurrent map writes")
    	}
    	alg := t.key.alg
    	hash := alg.hash(key, uintptr(h.hash0))
    
    	//map的标志位设置写状态
    	h.flags |= hashWriting
    
    	//如果桶数组为nil则分配1个
    	if h.buckets == nil {
    		h.buckets = newarray(t.bucket, 1)
    	}
    
    again:
    	//拿到key对应的桶数组地址
    	bucket := hash & (uintptr(1)<<h.B - 1)
    	//如果map处于扩容状态则尝试搬迁当前桶
    	if h.growing() {
    		//桶的搬迁工作,一次尝试搬迁两个桶,具体后续分析
    		growWork(t, h, bucket)
    	}
    	//以下部分和map的查找逻辑一样
    	b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))
    	top := uint8(hash >> (sys.PtrSize*8 - 8))
    	if top < minTopHash {
    		top += minTopHash
    	}
    
    	var inserti *uint8
    	var insertk unsafe.Pointer
    	var val unsafe.Pointer
    	for {
    		for i := uintptr(0); i < bucketCnt; i++ {
    			if b.tophash[i] != top {
    			//tophash不相等,则尝试看这个地址是否是空的
    				if b.tophash[i] == empty && inserti == nil {
    					//这个位置是空的并且inserti的指针为空,则先让inserti指向这个位置
    					//如果map中没有当前key则可以用这个空位置存储key
    					inserti = &b.tophash[i]
    					insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
    					val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
    				}
    				continue
    			}
    			//tophash一样,则通过equal看找到的k是否是key
    			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
    			if t.indirectkey {
    				k = *((*unsafe.Pointer)(k))
    			}
    			if !alg.equal(key, k) {
    				continue
    			}
    			//走到这一步,确定map中已经有key了,则需要更新key的值
    			//needkeyupdate判断是否需要重写key,不知道为什么会有重写key的操作?留坑
    			if t.needkeyupdate {
    				typedmemmove(t.key, k, key)
    			}
    			//val作为值的地址拿出来,跳转到结束阶段
    			val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
    			goto done
    		}
    		//这个桶里面没找打,则看看是否有溢出桶
    		ovf := b.overflow(t)
    		if ovf == nil {
    			//没有就跳出for,说明map里没有包含key
    			break
    		}
    		b = ovf
    	}
    
    	//map不在扩容状态,则判断是否需要扩容
    	//扩容准则1.是否超过转载因子 2.是否冗余溢出桶太多(原本很多key的map删除很多key之后的情况)
    	if !h.growing() && (overLoadFactor(int64(h.count), h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
    		//扩容前操作,后面会分析,就是分配新的地址空间为扩容的搬迁做准备
    		hashGrow(t, h)
    		//重新走上面流程
    		goto again // Growing the table invalidates everything, so try again
    	}
    
    	//上面的扩容流程之后的在此寻找,其实会进行两次搬迁操作后定位key
    	//map里没有key的话就会给inserti一个新的地址
    	//这里inserti等于nil则说明map没有达到扩容阈值,而桶和它的溢出桶的8个位置已经用完了,则需要再加一个溢出桶
    	if inserti == nil {
    		//申请一个新的溢出桶
    		newb := h.newoverflow(t, b)
    		//既然是新的,当然用第一位了
    		inserti = &newb.tophash[0]
    		insertk = add(unsafe.Pointer(newb), dataOffset)
    		val = add(insertk, bucketCnt*uintptr(t.keysize))
    	}
    
    	//到这里,说明key不存在map里,则新建对应的key和value
    	//key是指针,则申请一个key类型的数据
    	//insertk指向该数据的地址
    	if t.indirectkey {
    		kmem := newobject(t.key)
    		*(*unsafe.Pointer)(insertk) = kmem
    		insertk = kmem
    	}
    	//同理value一样
    	if t.indirectvalue {
    		vmem := newobject(t.elem)
    		*(*unsafe.Pointer)(val) = vmem
    	}
    	//拷贝key的值
    	typedmemmove(t.key, insertk, key)
    	//设置tophash
    	*inserti = top
    	//map的大小加一
    	h.count++
    
    done:
    	//判断写标记位是否存在,如果被清除了,说明并发写的情况
    	//直接抛错误
    	if h.flags&hashWriting == 0 {
    		throw("concurrent map writes")
    	}
    	//清除写标志位
    	h.flags &^= hashWriting
    	//
    	if t.indirectvalue {
    		val = *((*unsafe.Pointer)(val))
    	}
    	//返回val的地址,具体赋值是编译器的额外汇编指令处理
    	return val
    }
    

    map删除

    func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
    	//好像又是不要的部分代码
    	...
    
    	//map为nil或者带下为0则直接返回了
    	if h == nil || h.count == 0 {
    		return
    	}
    	//有协程在写,则抛异常,delete也是视为对map的写操作
    	if h.flags&hashWriting != 0 {
    		throw("concurrent map writes")
    	}
    
    	alg := t.key.alg
    	hash := alg.hash(key, uintptr(h.hash0))
    
    	//设置写标记位
    	h.flags |= hashWriting
    
    	bucket := hash & (uintptr(1)<<h.B - 1)
    	//如果是搬迁状态则尝试搬迁这个桶
    	if h.growing() {
    		growWork(t, h, bucket)
    	}
    	//以下阶段经典的找key阶段
    	b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))
    	top := uint8(hash >> (sys.PtrSize*8 - 8))
    	if top < minTopHash {
    		top += minTopHash
    	}
    	for {
    		for i := uintptr(0); i < bucketCnt; i++ {
    			if b.tophash[i] != top {
    				continue
    			}
    			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
    			k2 := k
    			if t.indirectkey {
    				k2 = *((*unsafe.Pointer)(k2))
    			}
    			if !alg.equal(key, k2) {
    				continue
    			}
    			if t.indirectkey {
    				*(*unsafe.Pointer)(k) = nil
    			} else {
    				typedmemclr(t.key, k)
    			}
    			v := unsafe.Pointer(uintptr(unsafe.Pointer(b)) + dataOffset + bucketCnt*uintptr(t.keysize) + i*uintptr(t.valuesize))
    			if t.indirectvalue {
    				*(*unsafe.Pointer)(v) = nil
    			} else {
    				typedmemclr(t.elem, v)
    			}
    			//对应tophash置空
    			b.tophash[i] = empty
    			//大小减一
    			h.count--
    			//调整结束阶段
    			goto done
    		}
    		b = b.overflow(t)
    		if b == nil {
    			goto done
    		}
    	}
    
    done:
    	//判断以下是否并发写
    	if h.flags&hashWriting == 0 {
    		throw("concurrent map writes")
    	}
    	//清空写标志位
    	h.flags &^= hashWriting
    }
    

    map扩容

    func hashGrow(t *maptype, h *hmap) {
    	bigger := uint8(1)
    	if !overLoadFactor(int64(h.count), h.B) {
    	//不是因为超过装载因子而扩容,则bigger置0
    		bigger = 0
    		//设置同大小扩容标志位
    		h.flags |= sameSizeGrow
    	}
    	//当前桶数组地址复制到老桶数组地址
    	oldbuckets := h.buckets
    	//申请新地址空间
    	newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger)
    
    	//当前flags清空两个标志位
    	flags := h.flags &^ (iterator | oldIterator)
    	//如果map有迭代器在读
    	if h.flags&iterator != 0 {
    		//flags的老迭代器标志位置1
    		flags |= oldIterator
    	}
    	//设置map的新属性值
    	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[0] != nil {
    		if h.extra.overflow[1] != nil {
    			throw("overflow is not nil")
    		}
    		h.extra.overflow[1] = h.extra.overflow[0]
    		h.extra.overflow[0] = nil
    	}
    	if nextOverflow != nil {
    		if h.extra == nil {
    			h.extra = new(mapextra)
    		}
    		h.extra.nextOverflow = nextOverflow
    	}
    }
    
    func (h *hmap) growing() bool {
    	return h.oldbuckets != nil
    }
    
    func growWork(t *maptype, h *hmap, bucket uintptr) {
    	//搬迁bucket桶
    	//和老桶掩码运算,确保搬迁老的桶下标
    	evacuate(t, h, bucket&h.oldbucketmask())
    
    	//没有搬迁完,则在搬一个,这次搬最小未搬迁的桶
    	if h.growing() {
    		evacuate(t, h, h.nevacuate)
    	}
    }
    
    func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
    	//获取老桶数组中oldbucket的桶地址
    	b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
    	//增长的大小,例如原先map的B=4,扩容后B=5则newbit=2^4  
    	//用于计算新搬迁桶地址以及相关标志位
    	newbit := h.noldbuckets()
    	alg := t.key.alg
    	//判断是否搬迁了
    	if !evacuated(b) {
    		var (
    			x, y   *bmap
    			xi, yi int
    			xk, yk unsafe.Pointer
    			xv, yv unsafe.Pointer
    		)
    		//x指向新桶数组中的老位置
    		x = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize)))
    		xi = 0
    		xk = add(unsafe.Pointer(x), dataOffset)
    		xv = add(xk, bucketCnt*uintptr(t.keysize))
    		if !h.sameSizeGrow() {
    			//真正扩容增长,y指向新桶数组的新位置
    			//和老位置的区别就是差newbit的个数
    			//按照取桶地址的方法,新B比老B多1位,也就是新的地址下标比老的下标多看hash值左边一位
    			y = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize)))
    			yi = 0
    			yk = add(unsafe.Pointer(y), dataOffset)
    			yv = add(yk, bucketCnt*uintptr(t.keysize))
    		}
    		//开始遍历b,进行搬迁
    		for ; b != nil; b = b.overflow(t) {
    			k := add(unsafe.Pointer(b), dataOffset)
    			v := add(k, bucketCnt*uintptr(t.keysize))
    			for i := 0; i < bucketCnt; i, k, v = i+1, add(k, uintptr(t.keysize)), add(v, uintptr(t.valuesize)) {
    				top := b.tophash[i]
    				if top == empty {
    					//标定搬迁空状态
    					b.tophash[i] = evacuatedEmpty
    					continue
    				}
    				//理论上不会出现小于minTopHash的值
    				if top < minTopHash {
    					throw("bad map state")
    				}
    				k2 := k
    				if t.indirectkey {
    					k2 = *((*unsafe.Pointer)(k2))
    				}
    				useX := true
    				if !h.sameSizeGrow() {
    					//计算hash值
    					hash := alg.hash(k2, uintptr(h.hash0))
    					//有迭代器访问的时候,这个和迭代器的遍历规则有关联,所以这里会判断
    					if h.flags&iterator != 0 {
    						//自己和自己对比,不一致,说明key是浮点NANs
    						if !t.reflexivekey && !alg.equal(k2, k2) {
    							//看旧桶的top值最后一位	
    							if top&1 != 0 {
    								//hash值在新B位置0,即将这个key放到新桶数组中的老位置
    								hash |= newbit
    							} else {
    								//放到新位置
    								hash &^= newbit
    							}
    							//记录新的top值
    							top = uint8(hash >> (sys.PtrSize*8 - 8))
    							//top修正
    							if top < minTopHash {
    								top += minTopHash
    							}
    						}
    					}
    					//判断是是否是0即是否在新桶数组的老位置
    					useX = hash&newbit == 0
    				}
    				if useX {
    					//老桶中的key的tophash标记位迁移到x,即新桶数组的老位置
    					b.tophash[i] = evacuatedX
    					//xi等于8了,说明这个桶装满了,需要溢出桶
    					if xi == bucketCnt {
    						newx := h.newoverflow(t, x)
    						x = newx
    						xi = 0
    						xk = add(unsafe.Pointer(x), dataOffset)
    						xv = add(xk, bucketCnt*uintptr(t.keysize))
    					}
    					//设定新的tophash
    					x.tophash[xi] = top
    					if t.indirectkey {
    						*(*unsafe.Pointer)(xk) = k2 
    					} else {
    						typedmemmove(t.key, xk, k) 
    					}
    					if t.indirectvalue {
    						*(*unsafe.Pointer)(xv) = *(*unsafe.Pointer)(v)
    					} else {
    						typedmemmove(t.elem, xv, v)
    					}
    					//下一个
    					xi++
    					xk = add(xk, uintptr(t.keysize))
    					xv = add(xv, uintptr(t.valuesize))
    				} else {
    					//同上,只是把key放在新桶数组中的新位置
    					b.tophash[i] = evacuatedY
    					if yi == bucketCnt {
    						newy := h.newoverflow(t, y)
    						y = newy
    						yi = 0
    						yk = add(unsafe.Pointer(y), dataOffset)
    						yv = add(yk, bucketCnt*uintptr(t.keysize))
    					}
    					y.tophash[yi] = top
    					if t.indirectkey {
    						*(*unsafe.Pointer)(yk) = k2
    					} else {
    						typedmemmove(t.key, yk, k)
    					}
    					if t.indirectvalue {
    						*(*unsafe.Pointer)(yv) = *(*unsafe.Pointer)(v)
    					} else {
    						typedmemmove(t.elem, yv, v)
    					}
    					yi++
    					yk = add(yk, uintptr(t.keysize))
    					yv = add(yv, uintptr(t.valuesize))
    				}
    			}
    		}
    		//判断是否老桶数组还有迭代器在访问
    		if h.flags&oldIterator == 0 {
    			//没有,则处理一下,帮助gc
    			b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
    			if t.bucket.kind&kindNoPointers == 0 {
    				memclrHasPointers(add(unsafe.Pointer(b), dataOffset), uintptr(t.bucketsize)-dataOffset)
    			} else {
    				memclrNoHeapPointers(add(unsafe.Pointer(b), dataOffset), uintptr(t.bucketsize)-dataOffset)
    			}
    		}
    	}
    
    	//刚好,上面处理的桶是最小未处理桶
    	if oldbucket == h.nevacuate {
    		//最小未处理桶往后走一位
    		h.nevacuate = oldbucket + 1
    		//尝试往后判断1024次,看看当前的最小未处理桶是否已经处理了,以及是否有连续的已经处理的桶
    		//有的话,就更新这个最小未处理桶下标,让这个数值精确
    		stop := h.nevacuate + 1024
    		if stop > newbit {
    			stop = newbit
    		}
    		for h.nevacuate != stop && bucketEvacuated(t, h, h.nevacuate) {
    			h.nevacuate++
    		}
    		//已经处理完了
    		if h.nevacuate == newbit {
    			//oldbuckets指向空
    			h.oldbuckets = nil
    			//指向空,让gc处理
    			if h.extra != nil {
    				h.extra.overflow[1] = nil
    			}
    			//标志位清空
    			h.flags &^= sameSizeGrow
    		}
    	}
    }
    

    map遍历

    //for range遍历的时候,先调用这个函数初始化一下迭代器
    func mapiterinit(t *maptype, h *hmap, it *hiter) {
    	it.key = nil
    	it.value = nil
    	it.t = nil
    	it.h = nil
    	it.buckets = nil
    	it.bptr = nil
    	it.overflow[0] = nil
    	it.overflow[1] = nil
    
    	//废弃代码?
    	...
    	//map空,或大小为0返回
    	if h == nil || h.count == 0 {
    		it.key = nil
    		it.value = nil
    		return
    	}
    
    	//检测
    	if unsafe.Sizeof(hiter{})/sys.PtrSize != 12 {
    		throw("hash_iter size incorrect") 
    	}
    	it.t = t
    	it.h = h
    
    	//记录当前map的B值
    	it.B = h.B
    	//记录当前桶数组地址
    	it.buckets = h.buckets
    	if t.bucket.kind&kindNoPointers != 0 {
    		//map不包含指针,则溢出桶地址在overflow中
    		h.createOverflow()
    		it.overflow = h.extra.overflow
    	}
    
    	//随机一个开始桶下标,以及桶内开始的下标
    	//所以map的遍历结果是变化的
    	r := uintptr(fastrand())
    	if h.B > 31-bucketCntBits {
    		r += uintptr(fastrand()) << 31
    	}
    	it.startBucket = r & (uintptr(1)<<h.B - 1)
    	it.offset = uint8(r >> h.B & (bucketCnt - 1))
    
    	it.bucket = it.startBucket
    	//判断是否已经走了一圈的标记
    	it.wrapped = false
    	it.bptr = nil
    
    	//不太懂,留坑,好像是允许多个迭代器同时遍历map
    	if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
    		atomic.Or8(&h.flags, iterator|oldIterator)
    	}
    	//遍历第一个元素
    	mapiternext(it)
    }
    
    func mapiternext(it *hiter) {
    	h := it.h
    	//废弃代码?
    	...
    	//不允许同时读写
    	if h.flags&hashWriting != 0 {
    		throw("concurrent map iteration and map write")
    	}
    	t := it.t
    	//记录当前遍历桶下标
    	bucket := it.bucket
    	//记录当前遍历桶地址
    	b := it.bptr
    	//记录桶内下标
    	i := it.i
    	//记录是否要检测新桶
    	checkBucket := it.checkBucket
    	alg := t.key.alg
    
    next:
    	if b == nil {
    		//刚开始,或者结束了
    		//走了一圈了,bucket又等于开始的下标,则表示已经遍历结束了
    		if bucket == it.startBucket && it.wrapped {
    			it.key = nil
    			it.value = nil
    			return
    		}
    		if h.growing() && it.B == h.B {
    			//当前map已经处在扩容阶段,但迭代器记录的B和map现在的B一样
    			//说明,迭代器是扩容阶段初始化的
    			oldbucket := bucket & it.h.oldbucketmask()
    			//b为老桶数组的相关地址
    			b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
    			//看是否已经搬迁了
    			if !evacuated(b) {
    				//还没有搬迁,则我们要遍历的数据还在老桶数组对应下标的桶内
    				//但老桶的数据不一定全是新桶数组的一样下标,需要判断这个值以后是不是会在我们当前遍历桶下标
    				//因此,记录需要检测的下标
    				checkBucket = bucket
    			} else {
    				//已经搬迁了,则遍历新桶数组对应的bucket下标桶数据
    				b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
    				checkBucket = noCheck
    			}
    		} else {
    			//情况1.迭代器是扩容前初始化的,处理直接遍历老桶数组,因为迭代器标准位的存在,老桶数组不会被清空
    			//情况2.map没有扩容,正常遍历桶数组
    			b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
    			//上面情况都不会存在需要检测的桶
    			checkBucket = noCheck
    		}
    		//bucket下一个,后续遍历准备
    		bucket++
    		//到头了,从0开始,并且循环标记为true
    		if bucket == uintptr(1)<<it.B {
    			bucket = 0
    			it.wrapped = true
    		}
    		//桶内遍历个数置0
    		i = 0
    	}
    	for ; i < bucketCnt; i++ {
    		//offi 桶内随机开始下标的快速定位
    		offi := (i + it.offset) & (bucketCnt - 1)
    		k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize))
    		v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(offi)*uintptr(t.valuesize))
    		//tophash为空,则跳过
    		if b.tophash[offi] != empty && b.tophash[offi] != evacuatedEmpty {
    			if checkBucket != noCheck && !h.sameSizeGrow() {
    				//是扩大一倍增长的,并且需要检测桶
    				k2 := k
    				if t.indirectkey {
    					k2 = *((*unsafe.Pointer)(k2))
    				}
    				if t.reflexivekey || alg.equal(k2, k2) {
    					//是正常key,但hash取得的桶下标不是目前需要的,则跳过
    					//说明这个key以后会去新的下标桶
    					hash := alg.hash(k2, uintptr(h.hash0))
    					if hash&(uintptr(1)<<it.B-1) != checkBucket {
    						continue
    					}
    				} else {
    					//特殊值nan的处理,我们是看tophash的最后一位,是否是1,来决定搬迁去向的
    					//因此,这个这次也这么判断,看这个key是否会在checkBucket下标的桶
    					if checkBucket>>(it.B-1) != uintptr(b.tophash[offi]&1) {
    						continue
    					}
    				}
    			}
    			//如果tophash不是特殊标记
    			if b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY {
    				//黄金数据,直接返回
    				if t.indirectkey {
    					k = *((*unsafe.Pointer)(k))
    				}
    				it.key = k
    				if t.indirectvalue {
    					v = *((*unsafe.Pointer)(v))
    				}
    				it.value = v
    			} else {
    				//有特殊标记,则说明这个被搬迁了
    				//因为map只有读互斥,所以我们需要去找一下这个key
    				//看是不是还存在
    				k2 := k
    				if t.indirectkey {
    					k2 = *((*unsafe.Pointer)(k2))
    				}
    				if t.reflexivekey || alg.equal(k2, k2) {
    					rk, rv := mapaccessK(t, h, k2)
    					if rk == nil {
    						continue // key has been deleted
    					}
    					it.key = rk
    					it.value = rv
    				} else {
    					//特殊的NANs的键值,不可能找到的,因此直接返回
    					it.key = k2
    					if t.indirectvalue {
    						v = *((*unsafe.Pointer)(v))
    					}
    					it.value = v
    				}
    			}
    			//找到一个数据了,相关字段写入迭代器,方便下一次迭代
    			it.bucket = bucket
    			if it.bptr != b { 
    				it.bptr = b
    			}
    			it.i = i + 1
    			it.checkBucket = checkBucket
    			return
    		}
    	}
    	//这个桶没有数据,看看溢出桶
    	b = b.overflow(t)
    	i = 0
    	goto next
    }
    

    参考

    1. 深度解密Go语言之map
    2. map解析
  • 相关阅读:
    白天写代码,晚上摆地摊!9年前摆地摊学会了这些道理...
    啪啪,打脸了!领导说:try-catch必须放在循环体外!
    阿里巴巴为什么让初始化集合时必须指定大小?
    永远不要使用双花括号初始化实例,否则就会OOM!
    限流的6大狠招,附详细代码和评测结果
    HashMap 的 7 种遍历方式与性能分析!(强烈推荐)
    String性能提升10倍的几个方法!(源码+原理分析)
    9个小技巧让你的 if else看起来更优雅
    用了自定义Banner后,SpringBoot瞬间变的高大上了...
    别再问我 new 字符串创建了几个对象了!我来证明给你看!
  • 原文地址:https://www.cnblogs.com/weiweng/p/12486356.html
Copyright © 2011-2022 走看看