map遍历 为何是无序的

91 阅读1分钟

参考自:煎鱼 关于这个技术点,网上文章不少,说法不一,我认为分析技术问题,分析源码的说服力最强。

源码路径:src/runtime/map.go,1400+行,代码量不少。

截图.png

因为存放时是无序的?

非也, 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,回显结果都不一样,是因为遍历的起始位置是随机的