底层结构

// map(集合)
type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    hash0     uint32 // hash seed

    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
    clearSeq   uint64

    extra *mapextra // optional fields
}
// buckets的结构
type bmap struct {
    topbits  [8]uint8
    keys     [8]keytype
    values   [8]valuetype
    pad      uintptr
    overflow uintptr
}

解释

Map中的数据存储在buckets和oldbuckets中,在发生扩容时,会创建新的buckets,并将老的buckets地址写入到oldbuckets中。

因此发生扩容后,存储数据会发生改变

疑问:那不发生扩容的情况下,我们遍历map为什么输出的顺序都不一致呢?

这是特意去设计的,Go在range遍历Map中元素的时候,从随机的一个位置开始迭代。

在Go1.0后的版本,Go遍历加入了随机数,输出的顺序将无序 源码链接:https://github.com/golang/go/blob/18b5435fc84225ca303da7a110c7e8065dc4bbda/src/runtime/map_noswiss.go#L912

func mapiterinit(t *maptype, h *hmap, it *hiter) {
    // ...
    // decide where to start
    r := uintptr(rand())
    // ...
}