参考自:煎鱼 关于这个技术点,网上文章不少,说法不一,我认为分析技术问题,分析源码的说服力最强。
源码路径:src/runtime/map.go,1400+行,代码量不少。
因为存放时是无序的?
非也, map数据安放好之后,除非发生扩容(hash冲突)等,导致数据重新排列,否则map内数据是按照一定顺序存放的(即 有序的)。
真正原因
mapiterinit函数作用为遍历迭代时进行初始化动作。有三个形参,用于读取当前hashTable的类型信息、当存储信息和当前遍历迭代的数据。
# go1.18
# src/runtime/map.go
func mapiterinit(t *maptype, h *hmap, it *hiter) {
if raceenabled && h != nil {
callerpc := getcallerpc()
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiterinit))
}
it.t = t
if h == nil || h.count == 0 {
return
}
if unsafe.Sizeof(hiter{})/goarch.PtrSize != 12 {
throw("hash_iter size incorrect") // see cmd/compile/internal/reflectdata/reflect.go
}
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 are added to the table
// while we are iterating.
h.createOverflow()
it.overflow = h.extra.overflow
it.oldoverflow = h.extra.oldoverflow
}
// decide where to start
r := uintptr(fastrand())
if h.B > 31-bucketCntBits {
r += uintptr(fastrand()) << 31
}
it.startBucket = r & bucketMask(h.B)
it.offset = uint8(r >> h.B & (bucketCnt - 1))
// iterator state
it.bucket = it.startBucket
// Remember we have an iterator.
// Can run concurrently with another mapiterinit().
if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
atomic.Or8(&h.flags, iterator|oldIterator)
}
mapiternext(it)
}
我们看↑代码"// decide where to start"部分,用到了fastrand函数生成随机数,用于选择一个bucket位置作为起始点进行遍历迭代。每次执行for range map,回显结果都不一样,是因为遍历的起始位置是随机的。