1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
// 向下寻找下一个key
func mapiternext(it *hiter) {
h := it.h // 获取当前hmap内存结构
if raceenabled {
callerpc := getcallerpc()
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiternext))
}
// 当前有hashWriting操作时,比如m[k]=v或delete函数时都会报错
if h.flags&hashWriting != 0 {
throw("concurrent map iteration and map write")
}
t := it.t // 获取map的结构
// 本次需要遍历的桶号
bucket := it.bucket // 当前的桶号
// 本次需要遍历的桶,如果为nil说明需要去bucket寻找
b := it.bptr // 当前存储桶
// 本次应该遍历的索引
i := it.i // 遍历索引默认0
checkBucket := it.checkBucket
// 遍历一遍当前桶及其溢出桶直到b=nil
next:
// 当前桶遍历完时,切换到下一个桶去遍历
if b == nil {
// 当前遍历的桶和开始的桶相等 并且 已经遍历过了最大桶数,说明遍历了一圈了
if bucket == it.startBucket && it.wrapped { // 结束遍历的条件
// end of iteration
it.key = nil
it.elem = nil
return
}
// h.growing() 当前正在扩容中
if h.growing() && it.B == h.B { // h.growing()当前map正处于扩容状态
// 迭代器是在增长过程中启动的,但增长尚未完成。
// 如果我们查看的bucket尚未填充(即,旧bucket未被清空),那么我们需要遍历旧buckets,只返回将迁移到此bucket的buckets。
oldbucket := bucket & it.h.oldbucketmask() // 旧桶的桶号
b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))) // 获取桶位置
// 当前桶的tophash[0]不是2|3|4时,说明数据没有被迁移
if !evacuated(b) {
checkBucket = bucket // 需要检查的桶号
} else { // 说明数据在新桶里面,数据已被迁移
b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
checkBucket = noCheck
}
} else { // 1.没有扩容 2.扩容了但是it.B != h.B
b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize))) // 新桶
checkBucket = noCheck
}
bucket++ // 桶号加一
// 当前桶号等于最大桶号
if bucket == bucketShift(it.B) { // bucketShift(it.B) 等于 1 >> B
bucket = 0 // 标记从0号桶开始
it.wrapped = true // 标记已经过了最大桶了
}
i = 0 // 新桶重置索引为0
}
// 遍历当前桶的所有元素 bucketCnt = 8
// 从当前桶遍历数据
for ; i < bucketCnt; i++ {
offi := (i + it.offset) & (bucketCnt - 1) // 根据it.offset偏移量开始随机遍历元素[0,7]
// b.tophash[offi] <= 1 或 evacuatedEmpty = 4 表示桶数据为空
if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty { // 如果当前位置为0或1或4表示桶为空
continue
}
k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize)) // 偏移到当前key位置
if t.indirectkey() { // 判断当前key存储是否是已指针存储而不是存储的key本身
k = *((*unsafe.Pointer)(k))
}
// 偏移到当前elem位置
e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(offi)*uintptr(t.elemsize))
// checkBucket != noCheck 数据在旧桶 并且 !h.sameSizeGrow() 翻倍扩容
// 需要验证桶时,旧桶存在数据
if checkBucket != noCheck && !h.sameSizeGrow() { // 存在没有迁移完的旧桶时,去检查旧桶
// t.reflexivekey() 为true表示 k == k 始终成立
// t.reflexivekey() || t.key.equal(k, k) -> k == k 始终成立情况
if t.reflexivekey() || t.key.equal(k, k) {
// 如果oldbucket中的项不是针对迭代中的当前新bucket,请跳过它
hash := t.hasher(k, uintptr(h.hash0))
if hash&bucketMask(it.B) != checkBucket { // 需要跳过的情况
continue
}
} else { // k == k 不是始终成立
// b.tophash[offi]&1 的最低位 参看数据迁移部分的去向
if checkBucket>>(it.B-1) != uintptr(b.tophash[offi]&1) {
continue
}
}
}
// 将找到的k和e保存到hiter中
// evacuatedX = 2、evacuatedY = 3
// b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY 存在有效数据
// !(t.reflexivekey() || t.key.equal(k, k)) -> x == x 不是始终成立
if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) ||
!(t.reflexivekey() || t.key.equal(k, k)) {
// 这是需要的数据,我们可以返回它
// 或
// key!=key,因此无法删除或更新条目,因此我们可以只返回它
// 这对我们来说很幸运,因为当key!=key关键是我们找不到它
it.key = k
if t.indirectelem() {
e = *((*unsafe.Pointer)(e))
}
it.elem = e
} else { // 数据被迁移到其他地方了
// 自从迭代器启动以来,哈希表一直在增长
// 这个key的数据现在在其他地方
// 检查数据的当前哈希表
// 此代码处理以下情况:
// 已被删除、更新或删除并重新插入
// 注意:我们需要重新标记密钥,因为它可能已更新为equal(),但不是相同的key(例如+0.0 vs-0.0)
rk, re := mapaccessK(t, h, k) // 根据key去寻找相应的数据,可能是新桶也可能是旧桶
// 每寻找到,可能key已被删除
if rk == nil {
continue // key has been deleted
}
it.key = rk
it.elem = re
}
it.bucket = bucket // 回写桶号
// 记录当前正在遍历的桶
if it.bptr != b { // avoid unnecessary write barrier; see issue 14921
it.bptr = b // 避免不必要的写入障碍
}
it.i = i + 1 // i加一
it.checkBucket = checkBucket
return
}
b = b.overflow(t) // 如果上面桶遍历完接到去后面桶找
i = 0
goto next
}
|