【go语言之map源码分析】

介绍

golang中的map是常用的数据结构,又称为hash表。接下来根据go源码,简单介绍一下go的map。

map结构

在go中实例化,关键字是map。但是在go中,会对go中是的hmap。

// 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
}

// mapextra holds fields that are not present on all maps.
type mapextra struct {
	// If both key and elem do not contain pointers and are inline, then we mark bucket
	// type as containing no pointers. This avoids scanning such maps.
	// However, bmap.overflow is a pointer. In order to keep overflow buckets
	// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
	// overflow and oldoverflow are only used if key and elem do not contain pointers.
	// overflow contains overflow buckets for hmap.buckets.
	// oldoverflow contains overflow buckets for hmap.oldbuckets.
	// The indirection allows to store a pointer to the slice in hiter.
	overflow    *[]*bmap
	oldoverflow *[]*bmap

	// nextOverflow holds a pointer to a free overflow bucket.
	nextOverflow *bmap
}

先把接下来的各个字段做一下简单的说明。

hmap

count

这个是map是有多少k/v键值对。

flags

这个是map当前是处于什么状态,主要是有

	iterator     = 1 // there may be an iterator using buckets 循环当前buckets
	oldIterator  = 2 // there may be an iterator using oldbuckets // 循环老的buckets
	hashWriting  = 4 // a goroutine is writing to the map //在写
	sameSizeGrow = 8 // the current map growth is to a new map of the same size // map在扩容

B

有多少buckets,注意是2的次方。buckets是真正装载key/val的地方。

noverflow

溢出桶的数量。注意是这个预估,并不是准确的值,当B的值小于16的时候是准确的,大于16的时候,是有概率的,见下图:
在这里插入图片描述

hash0

生成的随机数,用来生成key等。

buckets

类型是unsafe.Pointer。存放的buckets的地址的指针。

oldbuckets

类型是unsafe.Pointer。存放的是老的buckets的地址的指针,针对是老的buckets,针对扩容的时候。

nevacuate

扩容进度计数器。少于这个数字的buckets都会被回收。

mapextra

overflow

存放的所有溢出桶的地址。这个是为了保存所有溢出桶的存活,不被gc回收,因此用指针进行存放

oldoverflow

存放的所有老的溢出桶的地址。这个是针对扩容的时候,老的通上的溢出桶。这个是为了保存所有溢出桶的存活,不被gc回收,因此用指针进行存放

nextOverflow

指向的下一个溢出桶的地址。

上面是hmap的结构体,再看一下桶的结构体。

type bmap struct {
    tophash [bucketCnt]uint8
}
//编译期间展开如下
type bmap struct{
    topbits     [8]uint8     //用于表示标志位或hash值高八位来快速定位K/V位置
    keys        [8]keytype
    value       [8]valuetype
    pad         uintptr    //此字段go1.16.2版本已删除
    overflow    uintptr    //连接下个bmap溢出桶
}

所以这个bmap就是桶的大小。
topbits = 8字节
keys = 8*8=64字节
value = 8 * 8 = 64字节
overflow(64位) = 8字节

所以总共是=8+64+64+8 = 144字节

这个也可以通过断点看出。首先下面是go代码。

package main

func main() {
	m := make(map[int]int, 10)
	m[1] = 1
}

然后通过dlv断点打出makemap中的maptype
在这里插入图片描述
可以看出bucketsize就是144字节。

初始化

就是创建map,除了特别的初始化,调用的都是runtime下面的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 {
    // t是map中的元素类型
    // hint是就是make传的容量
    // 根据hint和t.bucket.size判断申请内存是否超过限制
	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()


    // 根据需要申请的数量得到B的数量
    // 如果hint大于8并且hint大于(1<<b)*6.5 就每次增长1,直接不满足
	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.
	// 如果是make(map[int]int)
	if h.B != 0 {
		var nextOverflow *bmap
		// 初始化buckets和溢出桶
		h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
		if nextOverflow != nil {
			h.extra = new(mapextra)
			h.extra.nextOverflow = nextOverflow
		}
	}

	return h
}


// makeBucketArray initializes a backing array for map buckets.
// 1<<b is the minimum number of buckets to allocate.
// dirtyalloc should either be nil or a bucket array previously
// allocated by makeBucketArray with the same t and b parameters.
// If dirtyalloc is nil a new backing array will be alloced and
// otherwise dirtyalloc will be cleared and reused as backing array.
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
    // 得到 1 << b 个buckets
	base := bucketShift(b)
	nbuckets := base
	// For small b, overflow buckets are unlikely.
	// Avoid the overhead of the calculation.
	// 如果大于4就需要分配溢出桶
	if b >= 4 {
		// Add on the estimated number of overflow buckets
		// required to insert the median number of elements
		// used with this value of b.
		// 增加额外的溢出桶1的数量
		nbuckets += bucketShift(b - 4)
        // 需要分配的内存大小
		sz := t.bucket.size * nbuckets
  
        // 获取的内存向上取整得到内存
		up := roundupsize(sz)
  
        // 不一致生成新的buckets数量
		if up != sz {
			nbuckets = up / t.bucket.size
		}
	}

	if dirtyalloc == nil {
	    // 申请新的内存数组
		buckets = newarray(t.bucket, int(nbuckets))
	} else {
		// dirtyalloc was previously generated by
		// the above newarray(t.bucket, int(nbuckets))
		// but may not be empty.
		buckets = dirtyalloc
		size := t.bucket.size * nbuckets
		if t.bucket.ptrdata != 0 {
			memclrHasPointers(buckets, size)
		} else {
			memclrNoHeapPointers(buckets, size)
		}
	}

	if base != nbuckets {
		// We preallocated some overflow buckets.
		// To keep the overhead of tracking these overflow buckets to a minimum,
		// we use the convention that if a preallocated overflow bucket's overflow
		// pointer is nil, then there are more available by bumping the pointer.
		// We need a safe non-nil pointer for the last overflow bucket; just use buckets.
  
        // 指针移动到下一个溢出桶
		nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))
		 
		// 指向最后一个buckets
		last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))
 
        // 将最后一个buckets的overflow指向第一个buckets
		last.setoverflow(t, (*bmap)(buckets))
	}
	return buckets, nextOverflow
}

添加值

这里的添加值就是在在hamp中添加值,代码如下

package main

import "fmt"

func main() {
	m := make(map[int]int, 10)
	m[1] = 1
}

还是一样通过反编译看出底层的调用是runtime.mapassign_fast64。接下来根据go的源码简单说一下mapassign_fast64这个方法。

func mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
    // 判断是否为nil
	if h == nil {
		panic(plainError("assignment to entry in nil map"))
	}
  
    // 判断竞争条件
	if raceenabled {
		callerpc := getcallerpc()
		racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapassign_fast64))
	}
   
    // 判断是否并发写
	if h.flags&hashWriting != 0 {
		fatal("concurrent map writes")
	}
   
    // 感觉key和随机种子生成hash的key
	hash := t.hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
   
    // h.flags 增加writing标志位
	h.flags ^= hashWriting
 
     // 如果bucket不存在,那么生成
	if h.buckets == nil {
		h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
	}

again:
    // 找到这个hash对应的桶
	bucket := hash & bucketMask(h.B)
  
    // 这个map是否在增长
	if h.growing() {
		growWork_fast64(t, h, bucket)
	}
  
    // 移动指针找到对应的buckets
	b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))

    // 初始化变量
	var insertb *bmap
	var inserti uintptr
	var insertk unsafe.Pointer

bucketloop:
	for {
		for i := uintptr(0); i < bucketCnt; i++ {
            // 判断是否为空 如果为空说明可以使用 
            // 判断b.tophash[i] 是否可用 这个为空和被删除等都是true 
            // 因为删除的时候会设置为1 为空是0
			if isEmpty(b.tophash[i]) {
			 
			    // 判断是否为nil
				if insertb == nil {
					insertb = b
					inserti = i
				}
				
				// 说明为空 直接跳出循环
				if b.tophash[i] == emptyRest {
					break bucketloop
				}
				continue
			}
            // 如果可以用 得到这个key上的指针对应的值
			k := *((*uint64)(add(unsafe.Pointer(b), dataOffset+i*8)))
			if k != key {
				continue
			}
			insertb = b
			inserti = i
			goto done
		}
   
        // 找到key后,判断是否有溢出桶,如果有溢出桶,那么使用溢出桶
		ovf := b.overflow(t)
		if ovf == nil {
			break
		}
		b = ovf
	}


     // 当出生以下情况 进行扩容
     // 没有处在扩容的状态
     // 超过了负载因子 key数量大于 1<<B *6.5或者有过多的溢出桶 大于 1 << (h.B&15)
	if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
	    // 进行扩容
		hashGrow(t, h)
		goto again 
	}
   
     // 如果insertb为nil
	if insertb == nil {
		// 生成一个溢出桶 并且将当前的buckets 关联过去
		insertb = h.newoverflow(t, b)
		inserti = 0 // not necessary, but avoids needlessly spilling inserti
	}
	// 将inserti的值放到tophash中
	insertb.tophash[inserti&(bucketCnt-1)] = tophash(hash) // mask inserti to avoid bounds checks
   
    // 找到桶的指针
	insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*8)
	
	// 将值存入
	*(*uint64)(insertk) = key
 
    // 数量加一
	h.count++

done:
    // 找到value所在的指针
	elem := add(unsafe.Pointer(insertb), dataOffset+bucketCnt*8+inserti*uintptr(t.elemsize))
	if h.flags&hashWriting == 0 {
		fatal("concurrent map writes")
	}
    // 将hashWriting从h.flags去掉
	h.flags &^= hashWriting
  
    // 返回
	return elem
}

这里不扩容版本的就说完了,这里只是把elem所在的指针返回,并没有进行赋值,看一下编译的代码
在这里插入图片描述
从汇编来看,最后通过汇编指令MOVD实现。

上面的流程是没有发生扩容,接下来看看扩容的流程。

扩容

首先是判断是否需要进行扩容,也就是hashGrow这个方法

func hashGrow(t *maptype, h *hmap) {
    // 默认是扩容一倍
	bigger := uint8(1)
  
    // 说明溢出桶过多了 会导致找到一个key的时间增加
	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标志位
	flags := h.flags &^ (iterator | oldIterator)
 
    // 如果iterator存在,认为是旧的,并放到flags
	if h.flags&iterator != 0 {
		flags |= oldIterator
	}
	// 赋值
	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
	}
    
    // 如果nextOverflow 存在 赋值给下一个
	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().

接下来就是搬迁的过程,go中的搬迁不是一次完成了,而且每次赋值的时候搬迁一些。
首先是判断是否在扩容,在mapassign_fast64中:
在这里插入图片描述

func growWork_fast64(t *maptype, h *hmap, bucket uintptr) {
    // bucket&h.oldbucketmask() 得到老的buckets所在的位置
    // 开始搬迁
	evacuate_fast64(t, h, bucket&h.oldbucketmask())

	// 如果没有搬迁完 那么在搬迁一次
	if h.growing() {
		evacuate_fast64(t, h, h.nevacuate)
	}
}

然后看一下真正的搬迁代码:

func evacuate_fast64(t *maptype, h *hmap, oldbucket uintptr) {
    // 获取需要搬迁的旧桶
	b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
  
    // 旧桶的buckets数量
	newbit := h.noldbuckets()
   
    // 是否需要搬迁 通过桶的tophash的第一位判断
	if !evacuated(b) {
        // 申明两个搬迁的桶 
        // evacDst is an evacuation destination.
/*type evacDst struct {
	b *bmap          // current destination bucket
	i int            // key/elem index into b
	k unsafe.Pointer // pointer to current key storage
	e unsafe.Pointer // pointer to current elem storage
}*/
		var xy [2]evacDst
   
        // 第一个搬迁的buckets 注意这里是oldbucket的位置,但是用的是h.buckets 而不是h.oldbuckets。所以是需要迁移的地址。
		x := &xy[0]
		x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize)))
		x.k = add(unsafe.Pointer(x.b), dataOffset)
		x.e = add(x.k, bucketCnt*8)
  
        // 如果发生了扩容 再申明一个buckets
		if !h.sameSizeGrow() {
		    // 注意是oldbucket+newbit 是在x的基础上增加了newbit
			y := &xy[1]
			y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize)))
			y.k = add(unsafe.Pointer(y.b), dataOffset)
			y.e = add(y.k, bucketCnt*8)
		}

        // 依次循环 当前bucktes和对应的溢出桶
		for ; b != nil; b = b.overflow(t) {
		    // 通过指针偏移 找到key的地址和value的地址
			k := add(unsafe.Pointer(b), dataOffset)
			e := add(k, bucketCnt*8)
  
            // 依次循环每个tophash 还是通过指针找到key和value
			for i := 0; i < bucketCnt; i, k, e = i+1, add(k, 8), add(e, uintptr(t.elemsize)) {
			    // 获取每个tophash 并且判断状态
				top := b.tophash[i]
				if isEmpty(top) {
					b.tophash[i] = evacuatedEmpty
					continue
				}
				if top < minTopHash {
					throw("bad map state")
				}
				var useY uint8
  
                // 如果扩容
				if !h.sameSizeGrow() {
				    // 获取hash值
					hash := t.hasher(k, uintptr(h.hash0))
					// 如果在老的命中了 存到新的桶中
					if hash&newbit != 0 {
						useY = 1
					}
				}
  
                // 存tophash的值 将老的tophash值设置成搬迁的状态
				b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY, enforced in makemap
				// 获取需要搬迁的地址
				dst := &xy[useY]                 // evacuation destination
 
                // 如果是已经是最后一个tophash了,说明需要溢出桶 
                // 创建溢出桶
				if dst.i == bucketCnt {
					dst.b = h.newoverflow(t, dst.b)
					dst.i = 0
					dst.k = add(unsafe.Pointer(dst.b), dataOffset)
					dst.e = add(dst.k, bucketCnt*8)
				}
				  
				// 将top值复制过去
				dst.b.tophash[dst.i&(bucketCnt-1)] = top // mask dst.i as an optimization, to avoid a bounds check

				// 复制key
				if t.key.ptrdata != 0 && writeBarrier.enabled {
					if goarch.PtrSize == 8 {
						// Write with a write barrier.
						*(*unsafe.Pointer)(dst.k) = *(*unsafe.Pointer)(k)
					} else {
						// There are three ways to squeeze at least one 32 bit pointer into 64 bits.
						// Give up and call typedmemmove.
						typedmemmove(t.key, dst.k, k)
					}
				} else {
					*(*uint64)(dst.k) = *(*uint64)(k)
				}
            
                 // 复制value
				typedmemmove(t.elem, dst.e, e)
                // 移动dst的i 
				dst.i++
			    
			    // 移动key和value的指针
				dst.k = add(dst.k, 8)
				dst.e = add(dst.e, uintptr(t.elemsize))
			}
		}
		 
		// 清除老的bucktes 是当前buckets
		if h.flags&oldIterator == 0 && t.bucket.ptrdata != 0 {
			b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))
			// Preserve b.tophash because the evacuation
			// state is maintained there.
			ptr := add(b, dataOffset)
			n := uintptr(t.bucketsize) - dataOffset
			memclrHasPointers(ptr, n)
		}
	}
 
    // 如果迁移结束了 那么把h.oldbuckets和h.extra.oldoverflow设置为nil
	if oldbucket == h.nevacuate {
		advanceEvacuationMark(h, t, newbit)
	}
}

如果已经搬迁完成,那么接下来再搬迁一次,注意调用的是h.nevacuate,也就是说肯定会走到advanceEvacuationMark这个方法中。

查询

map中的查询就是通过key查询map中的值,也就是如下的代码

package main

func main() {
	m := make(map[int]int, 10)
	_ = m[1]
}

通过汇编可以看出获取的底层调用是mapaccess1_fast64。其实是可以添加值的逻辑是类似的,接下来根据代码的逻辑梳理一遍。

func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
    // 竞性条件判断
	if raceenabled && h != nil {
		callerpc := getcallerpc()
		racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapaccess1_fast64))
	}
	// 如果h为nil或者没有数量返回默认的
	if h == nil || h.count == 0 {
		return unsafe.Pointer(&zeroVal[0])
	}
   
    // 判断当前是否有写操作 如果有直接panic 注意这个panic是无法被捕获
	if h.flags&hashWriting != 0 {
		fatal("concurrent map read and map write")
	}
	 
	// 初始化
	var b *bmap
    // 如果为0 说明就只有一个buckets
	if h.B == 0 {
		// One-bucket table. No need to hash.
		b = (*bmap)(h.buckets)
	} else {
	    // 生成hash值
		hash := t.hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
		// 获取buckets的数量 也就是 1 << h.B
		m := bucketMask(h.B)
        // 指针偏移找到当前的bmap
		b = (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
        // 如果存在老的buckets 先判断老的
		if c := h.oldbuckets; c != nil {
		    // 如果存在扩容 将m向右边偏移一位 也就是除2
			if !h.sameSizeGrow() {
				// There used to be half as many buckets; mask down one more power of two.
				m >>= 1
			}
			// 获取老的buckets所在的位置
			oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
            // 判断是否已经搬迁完成 如果没有 那么就用老的bucktes
			if !evacuated(oldb) {
				b = oldb
			}
		}
	}
	// 循环判断当前的buckets和溢出桶
	for ; b != nil; b = b.overflow(t) {
	    // 指针偏移找到key
		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 8) {
		    // 判断key是否一致 并且tophash存在 一致
		    // 那么通过指针偏移找到对应的值
			if *(*uint64)(k) == key && !isEmpty(b.tophash[i]) {
				return add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.elemsize))
			}
		}
	}
	return unsafe.Pointer(&zeroVal[0])
}

相比对添加值,这个查询值还是比较简单的

删除key

删除key的底层方法是delete,代码如下

package main

func main() {
	m := make(map[int]int, 10)
	delete(m, 1)
}

一样还是通过反编译,可以看出来底层的调用是mapdelete_fast64的方法。然后看一下这个方法的实现。

func mapdelete_fast64(t *maptype, h *hmap, key uint64) {
    // 判断竞争条件和map是否为nil
	if raceenabled && h != nil {
		callerpc := getcallerpc()
		racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapdelete_fast64))
	}
	if h == nil || h.count == 0 {
		return
	}
    // 如果有写那么抛出异常
	if h.flags&hashWriting != 0 {
		fatal("concurrent map writes")
	}
  
    // 根据key生成hash值
	hash := t.hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))

	// 给h.flags增加hashWriting这个标志位
	h.flags ^= hashWriting
  
    // 获取当前的bucket
	bucket := hash & bucketMask(h.B)
  
    // 判断是否有扩容 如果有扩容 去搬迁见添加值
	if h.growing() {
		growWork_fast64(t, h, bucket)
	}
  
    // 得到这个key对应的bucket
	b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
  
    // 得到一个临时的bucket
	bOrig := b
search:
    // 依次判断当前bucket和对应的溢出桶
	for ; b != nil; b = b.overflow(t) {
	    // 通过指针偏移,得到当前的key.因为是在64位平台,所以移动8字节
		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 8) {
		  
		    // 如果key一致并且tophash的非空
			if key != *(*uint64)(k) || isEmpty(b.tophash[i]) {
				continue
			}
			// 当key是指针的时候进行清空
			if t.key.ptrdata != 0 {
				if goarch.PtrSize == 8 {
					*(*unsafe.Pointer)(k) = nil
				} else {
					// There are three ways to squeeze at one ore more 32 bit pointers into 64 bits.
					// Just call memclrHasPointers instead of trying to handle all cases here.
					memclrHasPointers(k, 8)
				}
			}
            // 根据当前的bucket,和key找到对应的value
			e := add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.elemsize))
   
            // 清楚当前value
			if t.elem.ptrdata != 0 {
				memclrHasPointers(e, t.elem.size)
			} else {
				memclrNoHeapPointers(e, t.elem.size)
			}
          
            // 设置成emptyOne 注意没有设置成emptyRest 
            // 是因为设置成emptyOne可以后面重复利用
            // 如果不能重复利用 接下来会设置成emptyRest
			b.tophash[i] = emptyOne
		
		    // 如果是最后一个
			if i == bucketCnt-1 {
			    // 并且当前的bucket对应的溢出桶有在使用
			    // 那么说明当前的bucket是满员,那么直接跳过
				if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
					goto notLast
				}
			} else {
			    // 如果下一个tophash也是可以用,说明当前bucket是不需要进行回收,也跳过
				if b.tophash[i+1] != emptyRest {
					goto notLast
				}
			}
  
            // 说明有需要回收的
			for {
			    // 直接把当前的值设置成emptyRest
				b.tophash[i] = emptyRest
                // 因为是--,所以当为0的时候 判断是否是溢出桶
				if i == 0 {
					if b == bOrig {
						break // beginning of initial bucket, we're done.
					}
					// 得到前面一个溢出桶 然后依次判断
					c := b
					for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
					}
					i = bucketCnt - 1
				} else {
					i--
				}
				// 不为emptyOne 说明是有值 那么直接跳出循环
				if b.tophash[i] != emptyOne {
					break
				}
			}
		notLast:
		    // 把数量减一
			h.count--
			// Reset the hash seed to make it more difficult for attackers to
			// repeatedly trigger hash collisions. See issue 25237.
			if h.count == 0 {
				h.hash0 = fastrand()
			}
			break search
		}
	}
    // 再判断一次
	if h.flags&hashWriting == 0 {
		fatal("concurrent map writes")
	}
    // 将hashWriting 这个flag从h.flags中去掉
	h.flags &^= hashWriting
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值