作者:杜娟小乖_748 | 来源:互联网 | 2023-07-13 10:15
func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
if raceenabled && h != nil {
callerpc := getcallerpc()
pc := funcPC(mapdelete)
racewritepc(unsafe.Pointer(h), callerpc, pc)
raceReadObjectPC(t.key, key, callerpc, pc)
}
if msanenabled && h != nil {
msanread(key, t.key.size)
}
if h == nil || h.count == 0 {
if t.hashMightPanic() {
t.key.alg.hash(key, 0) // see issue 23734
}
return
}
if h.flags&hashWriting != 0 {
throw("concurrent map writes")
}
//go语言可以选择hash和equals的不同实现
alg := t.key.alg
//根据key获取hash值
hash := alg.hash(key, uintptr(h.hash0))
// Set hashWriting after calling alg.hash, since alg.hash may panic,
// in which case we have not actually done a write (delete).
//走到这一步说明我们对key计算hash值的时候没有发生panic,所以后边我们可以标识我们正在对
//此map进行操作
h.flags ^= hashWriting
//bucketMask(h.B)方法是生成B个1的二进制方法,比如B为5的话,则bucketMask(h.B)返回11111
然后与hash值进行与计算会获取到hash值的低B位的十进制数,也就是我们key在bucket数组里的角标,
//这个方法跟java语言里hashmap计算key位于哪个桶的方法是一样的,都是采用与运算代替取模计算(取模耗费性能),
//只不过java写法是hash&(B-1),bucketMask(h.B)方法的具体实现就是B-1
bucket := hash & bucketMask(h.B)
//go语言的map结构扩容时不会立即迁移所有的数据,跟redis一样会慢慢迁移,用到哪个bucket就会迁移哪个,
//下边代码就是判断此bucket是否在迁移,是的话会先迁移,然后再进行后续操作。
if h.growing() {
growWork(t, h, bucket)
}
//使用指针计算类似于start index*bucketLength,也即获取执行角标位置的链表的头节点指针
b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
//这个赋值是为下边的向前遍历链表赋值emptyRest做准备
bOrig := b
//计算hash值得高8位,tophash和key数组以及val数据的关系如下所示,因此我们只需要比对tophash
//就可以立即定位到key和val的位置
|tophash0 |tophash1 |tophash2 |tophash3 |tophash4 |tophash5 |tophash6 |tophash7 |
|k0 |k1 |k2 |k3 |k4 |k5 |k6 | k7 |
|val0 |val1 |val2 |val3 |val4 |val5 |val6 | val7 |
top := tophash(hash)
search:
//外层循环,依次循环bucket和溢出bucket
for ; b != nil; b = b.overflow(t) {
//内层循环,从角标0开始依次查找tophash是否在此bucket里
for i := uintptr(0); i //首先处理不存在时的逻辑
if b.tophash[i] != top {
//如果当前tophash值是emptyRest ,
//则代表i之后的所有当前bucket里tophash都是空且此bucket的溢出bucket里的tophash也是空
//(就是i之后的位置压根就没有被使用,类似slice的cap属性,redis里sds的free属性),
//因此后边就不用找了,直接退出循环
if b.tophash[i] == emptyRest {
break search
}
//否则继续查找
continue
}
//这里代表当前bucket里有和key的tophash值相同的tophash值,
//获取key的指针地址,首先定位bmap地址 偏移量(会偏移到k0初始地址) i*t.keysize
k := add(unsafe.Pointer(b), dataOffset i*uintptr(t.keysize))
k2 := k
//此处判断key是否是指针类型,是:获取到key值
if t.indirectkey() {
k2 = *((*unsafe.Pointer)(k2))
}
//使用之前选定的equals方法比较
if !alg.equal(key, k2) {
continue
}
// Only clear key if there are pointers in it.
//key是指针类型,只把指针置为nil,数据等待gc去处理
if t.indirectkey() {
*(*unsafe.Pointer)(k) = nil
} else if t.key.kind&kindNoPointers == 0 {
//key是非指针类型,清除数据
memclrHasPointers(k, t.key.size)
}
v := add(unsafe.Pointer(b), dataOffset bucketCnt*uintptr(t.keysize) i*uintptr(t.valuesize))
if t.indirectvalue() {
*(*unsafe.Pointer)(v) = nil
} else if t.elem.kind&kindNoPointers == 0 {
memclrHasPointers(v, t.elem.size)
} else {
memclrNoHeapPointers(v, t.elem.size)
}
//此时只可以确定当前位置为空了,
b.tophash[i] = emptyOne
// If the bucket now ends in a bunch of emptyOne states,
// change those to emptyRest states.
//如果当前的bucket以多个emptyOne 状态结尾,则把这些emptyOne 都改为emptyRest 状态
// It would be nice to make this a separate function, but
// for loops are not currently inlineable.
//如果i是7的话(就是在当前bucket的tophash的最后一个位置)
if i == bucketCnt-1 {
//如果当前bucket有溢出bucket且溢出bucket的tophash的0下标不是emptyRest,则不进行处理,直接结束外层循环
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
goto notLast
}
} else {
//
if b.tophash[i 1] != emptyRest {
goto notLast
}
}
//这里就是从当前i开始往前把连续的emptyOne都置为emptyRest
for {
b.tophash[i] = emptyRest
if i == 0 {
if b == bOrig {
break // beginning of initial bucket, we're done.
}
// Find previous bucket, continue at its last entry.
c := b
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
}
i = bucketCnt - 1
} else {
i--
}
//一旦不是,立即停止循环
if b.tophash[i] != emptyOne {
break
}
}
notLast:
//为hash表的count属性赋值
h.count--
break search
}
}
if h.flags&hashWriting == 0 {
throw("concurrent map writes")
}
h.flags &^= hashWriting
}