写在之前
在文章《go基础之map-写在前面(一)》的示例代码
for k, v := range m3 {
fmt.Println(k, v)
}
就是go的map的迭代方法,查看该代码的字节码,发现它调用了底层runtime.mapiterinit
方法。本篇会详细分析map的迭代方法。如果想详细查看源码的注释,可以查看我的GitHub,欢迎批评指正。我的打算是把一些常用的数据结构都分析一遍,如果有志同道合的人,可以联系我。
hiter结构体
这个结构体非常重要,该结构体记录了迭代过程中当前的信息,在迭代下个数据的时候会使用当前的hiter。让我们看看他的结构体:
// A hash iteration structure.
// If you modify hiter, also change cmd/compile/internal/gc/reflect.go to indicate
// the layout of this structure.
// 8 * 9 + 4 * 1 + (偏移4位) + 8 * 2 = 8 * 12
type hiter struct {
// 8个字节
key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/internal/gc/range.go).
// 8个字节
elem unsafe.Pointer // Must be in second position (see cmd/internal/gc/range.go).
// 8个字节
t *maptype
// 8个字节
h *hmap
// 8个字节
buckets unsafe.Pointer // bucket ptr at hash_iter initialization time
// 8个字节
bptr *bmap // current bucket
// 8个字节
overflow *[]*bmap // keeps overflow buckets of hmap.buckets alive
// 8个字节
oldoverflow *[]*bmap // keeps overflow buckets of hmap.oldbuckets alive
// 8个字节
startBucket uintptr // bucket iteration started at
// 1个字节
offset uint8 // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)
// 1个字节
wrapped bool // already wrapped around from end of bucket array to beginning
// 1个字节
B uint8
// 1个字节
i uint8
// 8个字节
bucket uintptr
// 8个字节
checkBucket uintptr
}
我用人话简答说明下字段的意思:
key
和elem
表示当前跌倒获取到的key和value,直白的说fmt.Println(k, v)
打印出来的值。buckets
就是hmap的buckets。bptr
比较清晰就是表示当前我正在迭代哪个bucket。overflow
和oldoverflow
很容易联想到和hamp的extral里面的overflow
和oldoverflow
,我这里忽略他就不分析了。startBucket
表示从哪个bucket开始迭代,这个startBucket
在每次迭代的开始设置的,而且这个值是不确定的。也就是说每次for k, v := range m3
的迭代打印出来的k的顺序很有可能都不是一致的。offset
表示从startBucket
这个bucket的第几个key开始迭代。wrapped
表示已经迭代到最后一个桶了。B
无需多说,i
表示当前迭代的桶的进度了。bucket
会记录当前迭代到哪个桶了,如果该值等于startBucket
值了,表示就迭代完毕了。checkBucket
这个字段的用处在下面的分析过程中说明,单独拎出来讲不清楚。
前景提要说明白了,就开始分析mapiterinit
这个入口方法了:
// mapiterinit initializes the hiter struct used for ranging over maps.
// The hiter struct pointed to by 'it' is allocated on the stack
// by the compilers order pass or on the heap by reflect_mapiterinit.
// Both need to have zeroed hiter since the struct contains pointers.
func mapiterinit(t *maptype, h *hmap, it *hiter) {
...
size := unsafe.Sizeof(hiter{
})
// 意义在哪里
if size/sys.PtrSize != 12 {
throw("hash_iter size incorrect") // see cmd/compile/internal/gc/reflect.go
}
it.t = t
it.h = h
// grab snapshot of bucket state
it.B = h.B
it.buckets = h.buckets
if t.bucket.ptrdata == 0 {
// Allocate the current slice and remember pointers to both current and old.
// This preserves all relevant overflow buckets alive even if
// the table grows and/or overflow buckets