1. 本篇介绍map的golang源码,全篇代码较多比较枯燥。
  2. map 包说明:
    1. map 只是一个 hash 表。数据被安排在桶数组中。每个桶最多包含8个 key/elem 对。
    2. hash 的low-order bits(hash&1<<B - 1)用于选择桶。每个桶包含 high-order bits(高8位)用于区分每个桶中的 entries(就是tophash)。
    3. 如果有超过 8 个 keys,hash 到一个桶中,我们将额外的 extra 桶链接起来。
    4. 当 hashtable 增长时,我们分配一个两倍大小的新桶数组。桶会从旧的桶数组渐进式复制到新的桶数组中。
    5. Map 迭代器遍历桶数组,并按遍历顺序返回keys。(常规桶后是按照溢出桶的顺序返回桶索引)
    6. 为了维护迭代语义,我们从不将 keys 移动到桶中(如果移动,键可能会返回0或2次)。
    7. 在增长 table 时,迭代器仍然在迭代旧表,并且必须检查新表是否已经移动(“evacuated”)到新表。
 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
// This file contains the implementation of Go's map type.
//
// A map is just a hash table. The data is arranged
// into an array of buckets. Each bucket contains up to
// 8 key/elem pairs. The low-order bits of the hash are
// used to select a bucket. Each bucket contains a few
// high-order bits of each hash to distinguish the entries
// within a single bucket.
//
// If more than 8 keys hash to a bucket, we chain on
// extra buckets.
//
// When the hashtable grows, we allocate a new array
// of buckets twice as big. Buckets are incrementally
// copied from the old bucket array to the new bucket array.
//
// Map iterators walk through the array of buckets and
// return the keys in walk order (bucket #, then overflow
// chain order, then bucket index).  To maintain iteration
// semantics, we never move keys within their bucket (if
// we did, keys might be returned 0 or 2 times).  When
// growing the table, iterators remain iterating through the
// old table and must check the new table if the bucket
// they are iterating through has been moved ("evacuated")
// to the new table.

// Picking loadFactor: too large and we have lots of overflow
// buckets, too small and we waste a lot of space. I wrote
// a simple program to check some stats for different loads:
// (64-bit, 8 byte keys and elems)
//
// Picking loadFactor: 太大会有很多溢出的桶,太小会浪费很多空间。
// 我写了一个简单的程序来检查不同加载的一些统计信息:(64-bit, 8 byte keys and elems)
//  loadFactor    %overflow  bytes/entry     hitprobe    missprobe
//        4.00         2.13        20.77         3.00         4.00
//        4.50         4.05        17.30         3.25         4.50
//        5.00         6.85        14.77         3.50         5.00
//        5.50        10.55        12.94         3.75         5.50
//        6.00        15.27        11.67         4.00         6.00
//        6.50        20.90        10.79         4.25         6.50  -- loadFactor = 13/2
//        7.00        27.14        10.15         4.50         7.00
//        7.50        34.03         9.73         4.75         7.50
//        8.00        41.10         9.40         5.00         8.00
//
// %overflow   = percentage of buckets which have an overflow bucket # 有溢出桶的桶的百分比
// bytes/entry = overhead bytes used per key/elem pair  # 每个 key/elem 对使用的开销字节数
// hitprobe    = # of entries to check when looking up a present key # 在查找当前键时要检查的条目
// missprobe   = # of entries to check when looking up an absent key # 在查找缺失键时要检查的条目
//
// Keep in mind this data is for maximally loaded tables, i.e. just
// before the table grows. Typical tables will be somewhat less loaded.

hmap 🚀

type hmap struct

结构说明:go的map中存储的是hmap的指针。map分配的是一块连续的内存,包括常规桶和溢出桶的内存。

  1. count:记录map中存储的键值对数量。该值也是len(map)返回值。cap()函数对map没有实际作用。
  2. flags:扩容、迭代相关标志位。
  3. B常规桶个数2^B,此处的B是幂次方。注意这里并没有包括溢出桶数量。
  4. noverflow:已使用的溢出桶数量,该值用于等量扩容的判断值。
  5. hash0:生成的随机值,扩容时候该值不会被刷新。但是在删除时map为空时会刷新。
  6. buckets:常规桶起始地址。
  7. oldbuckets:扩容时用于存储旧常规桶的起始地址也就是buckets的值,当oldbuckets != nil时也是扩容正在进行的条件。
  8. nevacuate:扩容正在进行中下一个要被迁移的旧桶编号。
  9. extra:溢出桶相关信息。
 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
// A header for a Go map.
type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    
    // 1. 已经存储的键值对个数,len(map) == count
    // 2. map不能使用cap()函数,cap()函数是计算容量,对于map来说意义不大
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8 // 当前hmap的状态,比如正处于写入数据中等,参看下面的常量
    // 【常规桶】个数等于 2^B,make()初始化时该值会被设置,注意这里并【没用】包含溢出桶的数量
    B         uint8  // log_2 of # of buckets (最多可以容纳 loadFactor * 2^B items)
    // 已使用的溢出桶数量,用于是否等量扩容判断,在make初始化和扩容初始化时被重置为0
    // 已使用溢出桶的计数规则:
    //  1. 常规桶 h.B <= 15 时,每使用了一个溢出桶 h.noverflow++
    //  2. 常规桶 h.B > 16 时,按照一定概率增加溢出桶 1/((1 << (h.B-15))-1) 的概率 h.noverflow++
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    // 在make初始化时根据随机数生成
    // hash随机数,用于生成key的hash值,make初始化时该值会被设置
    hash0     uint32 // hash seed	

    // 常规桶起始地址,make初始化时该值会被设置
    // 该值也许为nil,当count==0时,在【[key] = elem】时会初始化分配内存
    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    // 当存在扩容时会把hmap.buckets值拷贝到hmap.oldbuckets
    // 这也是判断扩容的条件 hmap.oldbuckets != nil
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    // 记录下一个要被迁移的旧桶编号,在扩容初始化时被重置为0,被分摊到到每次map操作中渐进迁移
    nevacuate  uintptr  // progress counter for evacuation (buckets less than this have been evacuated)

    // make初始化时extra.nextOverflow改值会被设置,记录溢出桶的首地址
    //  溢出桶分配规则常规桶 【h.B >= 4】,就会预分配 【h.B-4】 个溢出桶备用
    // extra 主要是记录分配的备用空闲溢出桶地址 extra.nextOverflow,以及分配过的无指针溢出桶记录在extra.overflow
    extra *mapextra // optional fields	溢出桶信息 以及 扩容信息
}

createOverflow()

  1. 创建一个 extra 或 extra.overflow。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func (h *hmap) createOverflow() {
    if h.extra == nil {	
        h.extra = new(mapextra)
    }
    if h.extra.overflow == nil {
        // slice多采用make()创建,这里采用new创建
        // 也就是只是创建24B大小内存,&slice{0,0,0}
        // 后续overflow字段会通过append()函数添加元素
        // 当bmap是指针类型时,用于存储溢出bmap,以免被GC给回收了
        h.extra.overflow = new([]*bmap) 
    }
}

growing()

  1. 判断当前map是否处于扩容状态,可能是翻倍扩容等量扩容
1
2
3
4
// growing reports whether h is growing. The growth may be to the same size or bigger.
func (h *hmap) growing() bool {
    return h.oldbuckets != nil // true.处于扩容状态
}

sameSizeGrow()

  1. sameSizeGrow 表示当前正在处于等量扩容状态。
1
2
3
4
// sameSizeGrow reports whether the current growth is to a map of the same size.
func (h *hmap) sameSizeGrow() bool {
    return h.flags&sameSizeGrow != 0 // true.等量扩容
}

noldbuckets()

  1. noldbuckets 计算当前 map 扩容之前的常规桶数。
1
2
3
4
5
6
7
8
// noldbuckets calculates the number of buckets prior to the current map growth.
func (h *hmap) noldbuckets() uintptr {
    oldB := h.B
    if !h.sameSizeGrow() {
        oldB--
    }
    return bucketShift(oldB) // 1 << B
}

oldbucketmask()

  1. 常规桶扩容之前的掩码数。
1
2
3
4
// oldbucketmask provides a mask that can be applied to calculate n % noldbuckets().
func (h *hmap) oldbucketmask() uintptr {
    return h.noldbuckets() - 1  // 桶增长之前的掩码
}

incrnoverflow()

  1. Incrnoverflow 增加 h.noverflow。noverflow 统计溢出桶的数量。
  2. 这用于触发等量扩容大小的map增长。等量扩容条件参看 tooManyOverflowBuckets 方法。
  3. 为了保持hmap较小,noverflow是uint16类型。
  4. 当桶很少时,noverflow是一个精确的计数。当有很多桶时,noverflow是一个近似的计数。
 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
// incrnoverflow increments h.noverflow.
// noverflow counts the number of overflow buckets.
// This is used to trigger same-size map growth.
// See also tooManyOverflowBuckets.
// To keep hmap small, noverflow is a uint16.
// When there are few buckets, noverflow is an exact count.
// When there are many buckets, noverflow is an approximate count.
func (h *hmap) incrnoverflow() {	// 增加使用的溢出桶计数器
    // We trigger same-size map growth if there are
    // as many overflow buckets as buckets.
    // We need to be able to count to 1<<h.B.
    // 
    // 如果溢出桶和常规桶一样多,我们就会触发等量扩容map。我们需要能够数到 1<<h.B。
    if h.B < 16 {       // 当桶的数量 <= 2^15 溢出桶加一
        h.noverflow++
        return
    }
    // Increment with probability 1/(1<<(h.B-15)).
    // When we reach 1<<15 - 1, we will have approximately
    // as many overflow buckets as buckets.
    // 
    // 以1/(1<<(h.B-15))概率递增加一
    mask := uint32(1)<<(h.B-15) - 1	
    // Example: if h.B == 18, then mask == 7,
    // and fastrand & 7 == 0 with probability 1/8.
    if fastrand()&mask == 0 {
        h.noverflow++
    }
}

newoverflow()

  1. 分配新的溢出桶,如果当前桶满了则需要分配溢出桶调用此方法返回溢出桶。
  2. 该方法有备用的溢出桶已用完重新分配溢出桶情况。
 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
func (h *hmap) newoverflow(t *maptype, b *bmap) *bmap {
    var ovf *bmap // 分配的溢出桶 *bmap
    // 当前溢出桶信息不为空 并且 下一个溢出桶不为空 直接从后面获取
    if h.extra != nil && h.extra.nextOverflow != nil {	
        // We have preallocated overflow buckets available.
        // See makeBucketArray for more details.
        // 
        // 我们有预先分配的溢出桶可用。更多细节请参见makeBucketArray方法。
        ovf = h.extra.nextOverflow
        // 判断溢出桶的overflow字段是否为nil,用于判断分配的备用溢出桶使用已用完
        // 参看makeBucketArray方法中把最后一个溢出桶overflow指向了第一个常规桶地址
        if ovf.overflow(t) == nil {
            // We're not at the end of the preallocated overflow buckets. Bump the pointer.
            //
            // 我们还没有到达预分配的溢出桶的末尾。碰一下指针。
            h.extra.nextOverflow = (*bmap)(add(unsafe.Pointer(ovf), uintptr(t.bucketsize)))	// 下一个空闲的溢出桶
        } else { // 已经是最后一个备用的溢出桶了(最后一个溢出桶指向了第一个常规桶,其他溢出桶都应该未nil)
            // This is the last preallocated overflow bucket.
            // Reset the overflow pointer on this bucket,
            // which was set to a non-nil sentinel value.
            //
            // 这是最后一个预分配的溢出桶。
            // 重置此桶上的溢出指针,该指针被设置为非nil哨兵值。
            ovf.setoverflow(t, nil) // ovf.overflow = nil
            h.extra.nextOverflow = nil
        }
    } else {
        // 溢出桶以分配完了 或者 没有分配溢出桶时
        // 注意:这里直接调用new()函数分配的
        ovf = (*bmap)(newobject(t.bucket))	
    }
    
    // 已使用溢出桶计数器
    h.incrnoverflow()	
    // 桶不包含指针
    if t.bucket.ptrdata == 0 {
        h.createOverflow()	// 防止 h.extra 或 h.extra.overflow 没有初始化
        // 将分配的溢出桶保存在h.extra.overflow中,主要是防止这部分内存不该被GC清理,结果被清除了
        *h.extra.overflow = append(*h.extra.overflow, ovf)
    }
    b.setoverflow(t, ovf)   // b.overflow = ovf
    return ovf
}

type bmap struct

  1. bmap 用来装 map 的桶。
  2. bmap 前8字节是 tophash 值,后面分别是8个连续的 key 和8个连续的 value 然后是一个溢出桶指针。
  3. 之所以桶按照这个结构设计是为了内存紧凑。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// A bucket for a Go map.
type bmap struct {
    // tophash generally contains the top byte of the hash value
    // for each key in this bucket. If tophash[0] < minTopHash,
    // tophash[0] is a bucket evacuation state instead.
    // 
    // Tophash通常包含该桶中每个键的散列值的顶部字节。
    // 如果tophash[0] < minTopHash,则tophash[0]为桶疏散状态。
    // const minTopHash = 5 
    // const bucketCnt = 8;
    tophash [bucketCnt]uint8    // [8]uint8
    // Followed by bucketCnt keys and then bucketCnt elems.
    // NOTE: packing all the keys together and then all the elems together makes the
    // code a bit more complicated than alternating key/elem/key/elem/... but it allows
    // us to eliminate padding which would be needed for, e.g., map[int64]int8.
    // Followed by an overflow pointer. 
    //
    // 接下来是bucketCnt的keys,然后是bucketCnt的elems。
    // 注意: 将所有key打包在一起,然后将所有elem打包在一起
    // 这比交替使用key/elem/key/elem/…但它允许我们消除所需的内边距,例如 map[int64]int8。
    // 后跟一个溢出指针。
}
  1. bmap 结构:
    1. h1 - h8:8个hash值分别是key的hash高八位,主要用于快速查找key和bmap桶的相关迁移状态标记。
    2. k1 - k8:8个key的值。
    3. v1 - v8:8个value的值。
    4. overflow:指向后面溢出桶的指针。

evacuated()

  1. 该 bmap 桶是否是疏散的,1 < tophash[0] < 5,表明该 bmap 桶数据已被迁移。
1
2
3
4
5
6
7
8
func evacuated(b *bmap) bool {
    h := b.tophash[0]
    // 参看后面 consts 中相关常量定义
    // const emptyOne = 1
    // const minTopHash = 5
    // return h IN (2,3,4)
    return h > emptyOne && h < minTopHash
}

overflow()

  1. 该 bmap 桶的 overflow 字段指向的溢出桶 *bmap
1
2
3
4
func (b *bmap) overflow(t *maptype) *bmap {
    // b + bucketsize - goarch.PtrSize
    return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-goarch.PtrSize))
}

setoverflow()

  1. 该 bmap 桶的 overflow 字段指向 ovf *bmap 这个桶。
  2. 等价于 bmap.overflow = ovf
1
2
3
4
func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
    // bmap.overflow = ovf
    *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-goarch.PtrSize)) = ovf
}

keys()

  1. 返回该 bmap 桶的 key 开始地址。
1
2
3
4
5
func (b *bmap) keys() unsafe.Pointer {
    // dataOffset 参看后面 consts 中的常量定义
    // dataOffset -> [8]uint8
    return add(unsafe.Pointer(b), dataOffset)
}

type mapextra struct

  1. Mapextra 持有一些在所有map中都不存在的字段。主要是溢出桶相关。
 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
// mapextra holds fields that are not present on all maps.
type mapextra struct {
    // If both key and elem do not contain pointers and are inline, then we mark bucket
    // type as containing no pointers. This avoids scanning such maps.
    // However, bmap.overflow is a pointer. In order to keep overflow buckets
    // alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
    // overflow and oldoverflow are only used if key and elem do not contain pointers.
    // overflow contains overflow buckets for hmap.buckets.
    // oldoverflow contains overflow buckets for hmap.oldbuckets.
    // The indirection allows to store a pointer to the slice in hiter.
    //
    // 如果 key 和 elem 都不包含指针并且是允许内联(inline)的,那么我们将bucket类型标记为【不包含指针】。这样避免了扫描这样的map。
    // 然而 bmap.overflow 是一个指针。
    // 为了保持overflow桶是alive(活跃的不被GC回收),我们将指向所有overflow桶的指针存储在hmap.extra.overflow和hmap.extra.oldoverflow中
    // 【仅当 key 和 elem 不包含指针时,才使用 overflow 和 oldoverflow。】因为不包含指针才容易被GC回收,因此需要保存。
    // overflow 包含用于 hmap.buckets 的overflow桶集
    // oldoverflow 包含用于 hmap.oldbuckets 的overflow桶集
    // 间接允许在hiter中存储一个指向切片的指针。(hiter是遍历相关结构)
    overflow    *[]*bmap // 在 key 和 elem 不包含指针时,把已经用到的溢出桶存储起来,主要是保存 alive
    oldoverflow *[]*bmap // 扩容正在进行时,把 overflow 数据拷贝到这里 

    // nextOverflow holds a pointer to a free overflow bucket.
    // 
    // nextOverflow保存了一个指向空闲溢出桶的指针。
    // 下一个尚未使用的溢出桶,这里桶之间是一个链表链接所有的溢出桶。
    // 该值在make函数中被设置成申请的首个溢出桶
    nextOverflow *bmap	
}

constant

 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
const (
    // Maximum number of key/elem pairs a bucket can hold.
    // 
    // 一个桶可以容纳的最大 key/elem 对数量
    bucketCntBits = 3
    bucketCnt     = 1 << bucketCntBits // 2^3 = 8

    // Maximum average load of a bucket that triggers growth is 6.5.
    // Represent as loadFactorNum/loadFactorDen, to allow integer math.
    // 
    // 触发增长的桶的最大平均负载为13/2 = 6.5。
    // 表示为 loadFactorNum/loadFactorDen ,允许进行整数运算。
    loadFactorNum = 13  // loadFactorNum/loadFactorDen = 6.5
    loadFactorDen = 2

    // Maximum key or elem size to keep inline (instead of mallocing per element).
    // Must fit in a uint8.
    // Fast versions cannot handle big elems - the cutoff size for
    // fast versions in cmd/compile/internal/gc/walk.go must be at most this elem.
    // 
    // 保存inline的最大 key 或 elem 大小(而不是为每个元素分配内存)
    // 必须适配在uint8中。快速版本不能处理大元素-在cmd/compile/internal/gc/walk中快速版本的截止大小。Go最多只能是这个elem。
    maxKeySize  = 128 // map键最大内存值 128B,超过该值则是桶的key是间接存储的,也就是只是存储的是指针
    maxElemSize = 128 // map值最大内存值 128B,超过该值则是桶的elem是间接存储的,也就是只是存储的是指针

    // data offset should be the size of the bmap struct, but needs to be
    // aligned correctly. For amd64p32 this means 64-bit alignment
    // even though pointers are 32 bit.
    // 
    // dataOffset 是bmap结构体的长度,但需要正确对齐
    // 对于amd64p32,这意味着即使指针是32位,也会进行64位对齐。
    dataOffset = unsafe.Offsetof(struct {
        b bmap      // 8B
        v int64     // 8B
    }{}.v) // dataOffset = 8B   bmap桶的第一个key偏移量

    // Possible tophash values. We reserve a few possibilities for special marks.
    // Each bucket (including its overflow buckets, if any) will have either all or none of its
    // entries in the evacuated* states (except during the evacuate() method, which only happens
    // during map writes and thus no one else can observe the map during that time).
    // 
    // 可能的 tophash 值。我们保留一些特殊标记的可能性。
    // 每个桶(包括它的溢出桶,如果有的话)的所有条目都将处于 evacuated* 状态(除非在使用evacuate()方法时,这种情况只发生在map写操作期间,
    // 因此,在这段时间内,没有其他人可以观察map)。
    // 当前格为空的默认状态,后面格都为空不存在其他数据
    emptyRest      = 0 // this cell is empty, and there are no more non-empty cells at higher indexes or overflows.
    // 当前格为空,后面格还存在其他数据。在当前格被删除时会被标记为1
    emptyOne       = 1 // this cell is empty
    // key/elem是有效的,entry已被疏散到翻倍后的新桶里,原来的桶号处
    evacuatedX     = 2 // key/elem is valid.  Entry has been evacuated to first half of larger table.
    // key/elem是有效的,entry已被疏散到翻倍后的新桶里,原来桶号的2倍处
    evacuatedY     = 3 // same as above, but evacuated to second half of larger table.
    // 单元格是空的。在扩容进行中被标记从 0或1标记为4
    evacuatedEmpty = 4 // cell is empty, bucket is evacuated.
    // 正常填充单元格的最小值,比如某个key计算后的hash值为0则填充为5。
    minTopHash     = 5 // minimum tophash for a normal filled cell.

    // flags
    // 可能有一个使用buckets的迭代器
    iterator     = 1 // there may be an iterator using buckets
    // 可能有一个使用oldbuckets的迭代器
    oldIterator  = 2 // there may be an iterator using oldbuckets
    // 一个goroutine正在向map写入数据,用于判断map是否在读写删除并发操作
    // 在m[key] = elem时被设置或在delete删除key时被设置
    hashWriting  = 4 // a goroutine is writing to the map
    // 等量扩容标志位
    sameSizeGrow = 8 // the current map growth is to a new map of the same size

    // sentinel bucket ID for iterator checks
    // 迭代器检查的哨兵桶ID,如果桶号为该值不需要检查
    // 迭代器相关,在扩容进行中迭代器用于检查的桶号
    noCheck = 1<<(8*goarch.PtrSize) - 1	
)

type maptype struct

  1. map 元类型,runtime/type.go
 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
type maptype struct {
    typ    _type        // map类型
    key    *_type       // key类型
    elem   *_type       // elem类型
    
    // 桶的类型,桶包含tophashs、keys、elems、overflow这四块
    // 由于key/elem是不确定的类型,所以bucket也是不同的类型
    // bucket是否包含指针类型,是取决于该结构中是否存在指针
    //	1. tophashs 是非指针
    //  2. keys、elems 是根据具体情况的
    //  3. overflow 是个指针,那么是否意味则bucket是否包含指针类型?
    // 其实bucket是否包含指针类型是根据keys、elems决定的,原因是overflow是用户操作不到的
    // 因此处理这个特殊情况时,采用了非指针形式采用mapextra结构保存溢出桶数据,以便不被GC回收
    bucket *_type // internal type representing a hash bucket
    // function for hashing keys (ptr to key, seed) -> hash
    // hash函数,用于(key, h.hash0)
    hasher     func(unsafe.Pointer, uintptr) uintptr	
    // size of key slot
    keysize    uint8  // key值大小
    // size of elem slot
    elemsize   uint8  // value值大小
    // size of bucket
    bucketsize uint16 // 桶大小
    flags      uint32 // map的标志位
}

indirectkey()

  1. 当 key 大于128B时key存的是它的指针,key 是否是间接存储的。采用这种间接存储是为了满足GC的位图。
  2. 一个桶最多支持 bucketSize*(1+maxKeySize+maxValSize)+ptrSize 字节,即2064字节,或258个指针大小的字,或33字节的指针位图。
  3. 因为键和值都小于128字节,所以可以保证它们有位图而不是GC程序。
1
2
3
4
5
6
// Note: flag values must match those used in the TMAP case
// in ../cmd/compile/internal/reflectdata/reflect.go:writeType.
func (mt *maptype) indirectkey() bool { // store ptr to key instead of key itself
    // flags = 1:key占用内存大于 128B
    return mt.flags&1 != 0	
}

indirectelem()

  1. 当elem大于128B时key存的是它的指针,elem是否是间接存储的。
1
2
3
4
func (mt *maptype) indirectelem() bool { // store ptr to elem instead of elem itself
    // flags = 2:elem占用内存大于 128B
    return mt.flags&2 != 0	
}

reflexivekey()

  1. map key是NaN时,判断 key 满足 x == x 成立?
  2. x != x 成立情况:
    • float32、float64、complex64、complex128(当为NaN时)。
    • any:存储的是 NaN 时。
    • Array 时判断数组的元素类型。切片不能作为map的key因此排除。
    • String 时,判断字段类型。
  3. 根据 IEEE754 标准,当浮点数的指数位全为1时,该数处于非正常是标准多用于表示正无穷大或负无穷大及NaN,因此其他位可能是不同的数值。
  4. x != x 情况是会影响hash,因此NaN的位值不一定全部相等。
1
2
3
4
func (mt *maptype) reflexivekey() bool { // true if k==k for all keys
    // flags = 4:说明 x == x始终是成立的
    return mt.flags&4 != 0	
}

needkeyupdate()

  1. map key的值需要更新,这种情况出现在 key == key,但是 key 生成的 hash 可能不相同,这会影响到 tophash 存储值。
  2. 需要更新的类型:
    • float32、float64、complex64、complex128(floats and complex can be +0/-0)。
      • 浮点数和复数可以是+0/-0,浮点数中这两个数保存的符号位是不同得,但是比较这两个数确实是相等的。
    • any 由于 any 能存储任何类型,所有可以存储浮点数等因此也需要更新。
    • string:(strings might have smaller backing stores)
      • 字符串可能有较小的后备存储器
    • Array 类型时判断数组元素,可能是上面类型。
    • Struct 时判断结构体的元素,可能是上面类型。
1
2
3
4
func (mt *maptype) needkeyupdate() bool { // true if we need to update key on an overwrite
    // flags = 8:需要更新slot的key
    return mt.flags&8 != 0	
}

hashMightPanic()

  1. key 生成 hash 可能发生 panic,这是由于slicemapfunction不能作为map key,而any可以存储任何类型。
  2. 存在可能的类型:
    • anyslicemapfunction; 当any存储的slicemapfunction时就会panic
    • Array 类型时判断数组的元素,该元素时slicemap或包含它们都会panicSlice 不能作为key
    • Struct 类型时判断结构体的元素,该元素时slicemap或包含它们都会panic
    • 但是如果是它们类型的指针则不会发生panic
1
2
3
4
func (mt *maptype) hashMightPanic() bool { // true if hash function might panic
    // flags = 16:key生成hash可能panic的
    return mt.flags&16 != 0	
}

make() 🚀

  1. makemap() 实现为 make(map[k]v, hint) 创建 Go map。
  2. 如果编译器已经确定可以在栈上创建map或第一个bucket,h and/or bucket 可能是 non-nil。
  3. 如果 h != nil,则可以直接在h中创建map。
  4. 如果 h.buckets != nil,则指向的桶可以用作第一个桶。
  5. 参数:
    • t *maptype:map 的元类型。
    • hint intkey/elem对的数量,不是桶的数量。hint能保证一定能存储key/elem对数据而不扩容。
    • h *hmap:map 指针,可能是 nil 或 non-nil。
  6. 返回值:
    • *hmap:生成的 map 的值,也就是 *hmap
 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
// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap {
    // MulUintptr 返回 a * b 以及乘法是否溢出
    //      mem = uintptr(hint) * t.bucket.size
    // 这里使用 key/elem 对乘上 桶大小 显示是在扩大计算内存,因为 hint 一定是大于 桶的数量的
    mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
    if overflow || mem > maxAlloc {
        hint = 0
    }

    // initialize Hmap
    if h == nil {
        // 初始化 hmap
        // 注意这里使用了new函数创建的
        h = new(hmap)	
    }
    // 生成一个随机数用于后续key的hash,该值在桶每次清空时该值会从新生成随机值
    h.hash0 = fastrand()

    // Find the size parameter B which will hold the requested # of elements.
    // For hint < 0 overLoadFactor returns false since hint < bucketCnt.
    // 
    // 找到参数B的大小,它将保存请求的元素
    // 对于 hint < 0 overLoadFactor 返回 false,因为 hint < bucketCnt。
    B := uint8(0)
    // 计算保存hint个key/elem对不扩容最小需要的B的数量
    for overLoadFactor(hint, B) {
        B++
    }
    h.B = B

    // allocate initial hash table
    // if B == 0, the buckets field is allocated lazily later (in mapassign)
    // If hint is large zeroing this memory could take a while.
    // 
    // 分配初始hash表,如果B == 0,则稍后(在mapassign中,也就是存储数据过程中)延迟分配buckets字段,
    // 如果hint很大,则清空该内存可能需要一段时间。
    if h.B != 0 {
        var nextOverflow *bmap  // 用于记录 makeBucketArray 是否分配了溢出桶
        // makeBucketArray 根据B分配常规桶和溢出桶,如果分配了溢出桶则将最后一个溢出桶地址指向第一个常规桶地址
        // 溢出桶分配规则 B >= 4 分配最少 B-4 个溢出桶备用,溢出桶数量 >= 1 << (B-4)
        h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
        if nextOverflow != nil {
            h.extra = new(mapextra)
            // 记录下一个可使用的溢出桶
            h.extra.nextOverflow = nextOverflow
        }
    }

    return h
}

overLoadFactor()

  1. overLoadFactor 报告放置在 1«B 桶中的项目数量是否超过 loadFactor。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
func overLoadFactor(count int, B uint8) bool {
    // const (
    //  bucketCnt = 8
    //  loadFactorNum = 13
    //  loadFactorDen = 2
    // )
    // bucketShift(B) == 1 << B
    // uintptr(count)/bucketShift(B) < loadFactorNum/loadFactorDen
    //
    // return count > 8 && count > 6.5 * (1 << B)
    return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

bucketShift()

  1. bucketShift 返回 1<<b,为代码生成做了优化。
1
2
3
4
5
6
7
8
// bucketShift returns 1<<b, optimized for code generation.
func bucketShift(b uint8) uintptr {
    // Masking the shift amount allows overflow checks to be elided.
    // 
    // 屏蔽移位量可以省略溢出检查。
    // goarch.PtrSize 64位下为8,32位下为4; goarch.PtrSize*8 - 1 掩码为了防止溢出
    return uintptr(1) << (b & (goarch.PtrSize*8 - 1))   // 1 << b
}

makeBucketArray()

  1. makeBucketArray()map 桶初始化一个后备数组。
  2. 1<<b是要分配的最小桶数。
  3. dirtyalloc要么是nil,要么是makeBucketArray之前分配的具有相同tb参数的桶数组。
  4. 如果dirtyallocnil,将分配一个新的后备数组,否则将清除dirtyalloc并作为后备数组重用。
  5. 参数:
    • t *maptypemap的类型结构。
    • b uint8h.B的值。
    • dirtyalloc unsafe.Pointernil时重新申请块内存,不是nil时,继续使用dirtyalloc 指向的内存地址。
  6. 返回值:
    • buckets unsafe.Pointer:分配的常规桶首地址。
    • nextOverflow *bmap:分配的溢出桶首地址。
 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
// makeBucketArray initializes a backing array for map buckets.
// 1<<b is the minimum number of buckets to allocate.
// dirtyalloc should either be nil or a bucket array previously
// allocated by makeBucketArray with the same t and b parameters.
// If dirtyalloc is nil a new backing array will be alloced and
// otherwise dirtyalloc will be cleared and reused as backing array.
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
    // base := 1 << b
    base := bucketShift(b) // 常规桶数量 
    nbuckets := base // 存储(常规桶 + 溢出桶)的总数量
    // For small b, overflow buckets are unlikely.
    // Avoid the overhead of the calculation.
    // 
    // 对于小值b来说,溢出桶不太可能出现。避免计算的开销。
    // 溢出桶分配规则 B >= 4 分配最少 B-4 个溢出桶备用。
    if b >= 4 {
        // Add on the estimated number of overflow buckets
        // required to insert the median number of elements
        // used with this value of b.
        // 
        // 再加上用该值b插入元素的中位数所需的溢出桶的估计数量。
        // nbuckets += 1 << (b - 4)
        nbuckets += bucketShift(b - 4) // 常规桶 + 溢出桶
        sz := t.bucket.size * nbuckets // 计算全部桶(常规桶 + 溢出桶)所需内存大小
        up := roundupsize(sz) // 匹配最接近sz的内存大小规格
        if up != sz { // 匹配的内存规格偏大	
            // 调整 nbuckets 数量,这里应该是偏大设置
            // 因为不能能匹配后出现偏小的情况
            nbuckets = up / t.bucket.size
        }
    }

    if dirtyalloc == nil {
        // 没有可重新利用的内存块,重新向内存管理系统申请内存
        // buckets 申请的桶首地址 包含常规桶和溢出桶,因此常规桶和溢出桶是一整块连续的内存
        buckets = newarray(t.bucket, int(nbuckets))	
    } else {
        // dirtyalloc was previously generated by
        // the above newarray(t.bucket, int(nbuckets))
        // but may not be empty.
        // 
        // dirtyalloc是由上述 newarray(t.bucket, int(nbuckets)) 生成的,但不能为空。
        buckets = dirtyalloc // 桶内存块地址
        size := t.bucket.size * nbuckets // 内存块大小
        if t.bucket.ptrdata != 0 {
            // 当前桶中存在指针数据时
            // 重置从buckets地址开始长度为size大小的内存
            memclrHasPointers(buckets, size)
        } else {
            // 当前桶中不存在指针数据时
            // 重置从buckets地址开始长度为size大小的内存
            memclrNoHeapPointers(buckets, size)
        }
    }

    // 分配了溢出桶
    if base != nbuckets {
        // We preallocated some overflow buckets.
        // To keep the overhead of tracking these overflow buckets to a minimum,
        // we use the convention that if a preallocated overflow bucket's overflow
        // pointer is nil, then there are more available by bumping the pointer.
        // We need a safe non-nil pointer for the last overflow bucket; just use buckets.
        //
        // 我们预先分配了一些溢出桶。
        // 为了将跟踪这些溢出桶的开销降到最低,我们使用约定:如果预分配的溢出桶的overflow指针为nil,则通过碰撞指针来提供更多可用的内存。
        // 我们需要为最后一个溢出桶提供一个安全的 non-nil 指针; 就用常规桶吧。用于在分配溢出桶时判断溢出桶以分配完
        nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize))) // 首个溢出桶 *bmap
        last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))// 最后一个溢出桶 *bmap
        // 之所以将最后一个溢出桶overflow指向第一个常规桶地址是用于分配溢出桶是判断是否分配完参看(*hmap).newoverflow()方法
        last.setoverflow(t, (*bmap)(buckets))   // 最后一个溢出桶指针指向首个常规桶
    }
    return buckets, nextOverflow
}

newarray()

  1. newarray() 分配一个包含 n 个类型为 typ 的元素的数组。
  2. 参数:
    • typ *_type:桶的类型结构。
    • n int:桶的个数(常规桶 + 溢出桶)总数。
  3. 返回值:
    • unsafe.Pointer:申请到的内存首地址。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// newarray allocates an array of n elements of type typ.
func newarray(typ *_type, n int) unsafe.Pointer {
    if n == 1 {
        // 只有一个桶,直接申请
        return mallocgc(typ.size, typ, true)
    }
    // 溢出判断,typ.size * uintptr(n) 为桶总共占用的内存
    mem, overflow := math.MulUintptr(typ.size, uintptr(n))
    if overflow || mem > maxAlloc || n < 0 {
        panic(plainError("runtime: allocation size out of range"))
    }
    return mallocgc(mem, typ, true)
}

makemap_small()

  1. makemap_small() 实现了 make(map[k]v)make(map[k]v, hint)Go map创建。
  2. hint(<= 8)在编译时已知最多为 bucketCnt (8),并且需要在堆上分配map时。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// makemap_small implements Go map creation for make(map[k]v) and
// make(map[k]v, hint) when hint is known to be at most bucketCnt
// at compile time and the map needs to be allocated on the heap.
func makemap_small() *hmap {
    // 当 hint <= 8 时
    // 初始化 map
    h := new(hmap)
    h.hash0 = fastrand()
    return h
}

map Get 🚀

  1. 获取 map 中指定 key 的值

v := h[key]

mapaccess1_fat()

  1. 可以指定默认值 zero,当分0内存是返回zero值。
  2. 该函数返回一个地址,地址指向的值就是要取得v值。
  3. 参数:
    • t *maptypemap的类型结构。
    • h *hmapmap的内存结构。
    • key unsafe.Pointerkey的地址。
    • zero unsafe.Pointer:零值地址,用于返回值。
  4. 返回值:
    • unsafe.Pointer:获取到的elem地址。
1
2
3
4
5
6
7
func mapaccess1_fat(t *maptype, h *hmap, key, zero unsafe.Pointer) unsafe.Pointer {
    e := mapaccess1(t, h, key)
    if e == unsafe.Pointer(&zeroVal[0]) {
        return zero
    }
    return e
}

mapaccess1()

  1. mapaccess1() 返回一个指向h[key]的指针。
  2. 永远不要返回nil,相反,如果key不在map中,它会返回一个指向elem类型的zero对象的引用。
    • zero对象就是全局变量&zeroVal[0]地址值。
  3. 注意:返回的指针可能会使整个map保持活动状态,所以不要占用它太长时间。
  4. go map 是不支持多线程的写,但是多线程的读是没有问题的。
  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
// mapaccess1 returns a pointer to h[key].  Never returns nil, instead
// it will return a reference to the zero object for the elem type if
// the key is not in the map.
// NOTE: The returned pointer may keep the whole map live, so don't
// hold onto it for very long.
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    if raceenabled && h != nil {
        callerpc := getcallerpc()
        pc := abi.FuncPCABIInternal(mapaccess1)
        racereadpc(unsafe.Pointer(h), callerpc, pc)
        raceReadObjectPC(t.key, key, callerpc, pc)
    }
    if msanenabled && h != nil {
        msanread(key, t.key.size)
    }
    if asanenabled && h != nil {
        asanread(key, t.key.size)
    }
    
    // 1) map未初始化 或 没有key/elem对
    if h == nil || h.count == 0 {
        // 当key为any类型时,在key经过hash时可能会发生panic
        // t.hashMightPanic()判断当前hash函数是否可能发生panic
        // 这是因为 any 存储的值可能不适合作为 map 的 key
        if t.hashMightPanic() {
            // 尝试hash panic
            t.hasher(key, 0) // see issue 23734
        }
        // 直接返回 &zeroVal[0],返回默认值
        return unsafe.Pointer(&zeroVal[0])
    }
    
    // 2) 当前map存在其他goroutine正在写操作,直接报错不支持并发
    // hashWriting 标志在写map或delete map时被设置
    if h.flags&hashWriting != 0 {
        fatal("concurrent map read and map write")  // 并发的map读写操作
    }
    
    // 3) key根据h.hash0生成hash值,根据上面key为any时这里hash可能会panic
    hash := t.hasher(key, uintptr(h.hash0))
    m := bucketMask(h.B) // map常规桶数量; m = (1 << B) - 1
    // 4) 偏移到当前key对应的常规桶处 *bmap
    b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
    // 5) c != nil 当前map正在扩容中,需要判断当前旧桶数据是否迁移,没迁移数据还在旧桶中
    if c := h.oldbuckets; c != nil {
        // !h.sameSizeGrow() == true; 
        // 翻倍扩容时
        if !h.sameSizeGrow() {
            // There used to be half as many buckets; mask down one more power of two.
            // 
            // 过去桶的数量只有现在的一半;再除一个2的倍数。
            m >>= 1 // 需要把m缩小两倍
        }
        oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
        // 判断旧桶是否已经迁移了,没有迁移则从旧桶里面取数据
        // 不是2|3|4时表示数据还在旧桶里面,tophash[0] NOT IN (2,3,4)
        if !evacuated(oldb) {
            b = oldb    // 替换为旧桶 *bmap
        }
    }
    
    // 6) top 是 hash 的高八位值,用于快速比对 key
    // hash >> (8*8 - 8)
    top := tophash(hash) // uint8
bucketloop:
    // 7) 遍历当前桶以及后面的溢出桶
    for ; b != nil; b = b.overflow(t) {
        // 遍历当前桶; bucketCnt = 8
        for i := uintptr(0); i < bucketCnt; i++ {
            // 7.1) 当前 tophash 不相等
            if b.tophash[i] != top {
                // const emptyRest = 0
                // 当前桶包括溢出桶后面没有数据了
                if b.tophash[i] == emptyRest {
                    break bucketloop    // 结束遍历
                }
                continue
            }
            
            // 7.2) b.tophash[i] == top; 并不代表一定找到key,可能出现tophash冲突情况
            k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize)) // 找到当前key的地址
            // key的数据是间接存储的,关于常量maxKeySize的相关情况
            if t.indirectkey() {
                k = *((*unsafe.Pointer)(k)) // 拿到key地址
            }
            // 7.3) 调用key的比较函数,比较key和k是否相等。
            // 这里也是key必须是可比较类型的原因
            // 不可比较的类型:
            //  1. map,以及包含map的结构
            //  2. slice,以及包含slice的结构
            // 这里key如果是 NaN 的话比对不通过。
            if t.key.equal(key, k) {
                // 比对成功。偏移到elem元素地址处
                e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
                // elem的数据是间接存储的,关于常量maxElemSize的相关情况
                if t.indirectelem() {
                    e = *((*unsafe.Pointer)(e)) // 拿到elem地址
                }
                return e    // 成功返回找到的elem地址指针
            }
            
            // 未匹配成功出现tophash【冲突】情况
        }
    }
    // map 中没有 key,返回默认值
    return unsafe.Pointer(&zeroVal[0])
}

bucketMask()

  1. bucketMask 返回1<<b - 1,为代码生成做了优化。
1
2
3
4
// bucketMask returns 1<<b - 1, optimized for code generation.
func bucketMask(b uint8) uintptr {
    return bucketShift(b) - 1 // (1<<b) - 1
}

tophash()

  1. tophash 计算 hash 的 Tophash 值。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// tophash calculates the tophash value for hash.
func tophash(hash uintptr) uint8 {
    // hash >> (8*8 - 8)
    // hash高八位
    top := uint8(hash >> (goarch.PtrSize*8 - 8))	
    // top < 5
    if top < minTopHash {
        top += minTopHash
    }
    return top
}

evacuated()

  1. 桶数据是否已迁移。
1
2
3
4
5
func evacuated(b *bmap) bool {
    h := b.tophash[0]
    // b.tophash[0] IN (2,3,4)
    return h > emptyOne && h < minTopHash
}

v, b := h[key]

mapaccess2_fat()

  1. 可以指定默认值 zero。当分0内存是返回 zero 值。
1
2
3
4
5
6
7
8
9
func mapaccess2_fat(t *maptype, h *hmap, key, zero unsafe.Pointer) (unsafe.Pointer, bool) {
    // 没看错,这里依然是调用的 mapaccess1() 函数
    // 因为如果没找到key会返回&zeroVal[0],根据该值能判断bool。
    e := mapaccess1(t, h, key)
    if e == unsafe.Pointer(&zeroVal[0]) {
        return zero, false
    }
    return e, true
}

mapaccess2()

  1. mapaccess2() 和 mapaccess1() 代码基本一样,只多返回个获取成失败的值。
  2. 该函数在反射相关中被调用,正常map这里不会调用该函数。
 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
func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool) {
    if raceenabled && h != nil {
        callerpc := getcallerpc()
        pc := abi.FuncPCABIInternal(mapaccess2)
        racereadpc(unsafe.Pointer(h), callerpc, pc)
        raceReadObjectPC(t.key, key, callerpc, pc)
    }
    if msanenabled && h != nil {
        msanread(key, t.key.size)
    }
    if asanenabled && h != nil {
        asanread(key, t.key.size)
    }
    if h == nil || h.count == 0 {
        if t.hashMightPanic() {
            t.hasher(key, 0) // see issue 23734
        }
        return unsafe.Pointer(&zeroVal[0]), false // 这里获取失败返回 false
    }
    if h.flags&hashWriting != 0 {
        fatal("concurrent map read and map write")
    }
    hash := t.hasher(key, uintptr(h.hash0))
    m := bucketMask(h.B)
    b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
    if c := h.oldbuckets; c != nil {
        if !h.sameSizeGrow() {
            // There used to be half as many buckets; mask down one more power of two.
            m >>= 1
        }
        oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
        if !evacuated(oldb) {
            b = oldb
        }
    }
    top := tophash(hash)
bucketloop:
    for ; b != nil; b = b.overflow(t) {
        for i := uintptr(0); i < bucketCnt; i++ {
            if b.tophash[i] != top {
                if b.tophash[i] == emptyRest {
                    break bucketloop
                }
                continue
            }
            k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
            if t.indirectkey() {
                k = *((*unsafe.Pointer)(k))
            }
            if t.key.equal(key, k) {
                e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
                if t.indirectelem() {
                    e = *((*unsafe.Pointer)(e))
                }
                return e, true // 这里获取成功返回 true
            }
        }
    }
    return unsafe.Pointer(&zeroVal[0]), false // 这里获取失败返回 false
}

map Set 🚀

h[k] = v

reflect_mapassign()

  1. keyelem分别代表的是各自的地址。
  2. key != key 时,存入的数据for range情况是取不出来的。
  3. 参数:
    • t *maptypemap类型结构。
    • h *hmapmap内存结构。
    • key unsafe.Pointermapkey地址。
    • elem unsafe.Pointermap要写入的v地址。
1
2
3
4
5
6
7
//go:linkname reflect_mapassign reflect.mapassign
func reflect_mapassign(t *maptype, h *hmap, key unsafe.Pointer, elem unsafe.Pointer) {
    // 从map中找到elem应该放入插槽的地址
    p := mapassign(t, h, key)	
    // 拷贝数据,也就四把&v复制给map插槽
    typedmemmove(t.elem, p, elem)	
}

mapassign()

  1. 类似于mapaccess,但如果key在map中不存在,则会为键分配一个位置。
  2. 存在两种情况:
    • 新增:可能k是从来没存在map中。
    • 修改:可能k已经存储在map中。
  3. map并发不安全的。
  4. map 必须要经过make()函数初始化才能使用。
  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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    // 1) 未初始化的map不允许写操作
    // 这里就是为什么向nil的map写入数据会panic原因
    if h == nil {
        panic(plainError("assignment to entry in nil map"))
    }
    if raceenabled {
        callerpc := getcallerpc()
        pc := abi.FuncPCABIInternal(mapassign)
        racewritepc(unsafe.Pointer(h), callerpc, pc)
        raceReadObjectPC(t.key, key, callerpc, pc)
    }
    if msanenabled {
        msanread(key, t.key.size)
    }
    if asanenabled {
        asanread(key, t.key.size)
    }
    // 2) 检查map是否存在并发的写操作,删除也是写操作。
    if h.flags&hashWriting != 0 {
        fatal("concurrent map writes")
    }
    // 3) key根据h.hash0生成hash值,根据上面key为any时这里hash可能会panic。
    hash := t.hasher(key, uintptr(h.hash0))

    // Set hashWriting after calling t.hasher, since t.hasher may panic,
    // in which case we have not actually done a write.
    //
    // 在调用t.hasher之后设置hashWriting,因为t.hasher可能会出错,在这种情况下我们实际上并没有写入。
    // 这里使用 ^= 有点小窍门,因为在并发写的情况下可能存在上面的h.flags&hashWriting != 0检查时被跳过了,
    // 所以这个函数最后还需要判断并是否已经发生 ^ 异或 相同得0,不同得1; hashWriting = 4 = 0b100
    // 按照正常逻辑 h.flags 的3bit位应该是0,那么0^1=1。在函数的最后 h.flags&hashWriting == 0 判断时 1&1=1。
    // 异常逻辑 h.flags 的3bit位是1,那么 1^1=0。在函数的最后 h.flags&hashWriting == 0 判断时 0&1=0。因此判断并发发生
    h.flags ^= hashWriting  // 写状态标记

    // 4) 没有分配常规桶,这种情况来自makemap_small()函数,hint<=8 时
    if h.buckets == nil {
        // 申请一个常规桶内存块
        h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
    }

again:
    // 5) 根据 hash 映射到指定桶号
    bucket := hash & bucketMask(h.B) // bucket = hash & ((1 << B) - 1)
    // 6) 是否正处于扩容中:【等量扩容】 或 【翻倍扩容】
    if h.growing() {    // h.oldbuckets != nil
        // 迁移当前key对应的桶数据到新桶完成部分扩容任务
        // 迁移后会把旧桶数据迁移到新桶,因此以下代码处理新桶即可
        // growWork() 函数在delete(map)和map[k]=v时被调用,渐进式迁移数据
        growWork(t, h, bucket)	
    }
    
    // 7) 寻找对应的桶(*bmap),key对应的桶 *bmap
    b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
    // top = uint8( hash & (1>>8*8-8) )
    top := tophash(hash)    // hash 高八位,用于快速判断 key

    // inserti 对应key应该放入tophash的slot位置地址
    var inserti *uint8	
    // insertk 对应key应该放入keys的slot位置地址
    var insertk unsafe.Pointer
    // elem 对应key应该放入elems的slot位置地址
    var elem unsafe.Pointer	
bucketloop:
    // 8) 遍历常规桶和溢出桶,寻找slot位置
    // 被删除的slot会被优先使用来存储数据
    for {
        // 8.1) 遍历单个桶; const bucketCnt = 8
        for i := uintptr(0); i < bucketCnt; i++ {
            // 8.1.1) tophash 比对失败
            if b.tophash[i] != top {
                // isEmpty(b.tophash[i]) --> b.tophash[i] <= 1
                // inserti == nil; 寻找到slot后不会再进入这里
                // 这里也是判断当前可能是新增的情况,当前位可以插入数据如果是新增的话。
                if isEmpty(b.tophash[i]) && inserti == nil {
                    // 记录当前插槽位置
                    inserti = &b.tophash[i]
                    insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
                    elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
                }
                
                // b.tophash[i] == 0; 后面没有数据了
                if b.tophash[i] == emptyRest {
                    break bucketloop
                }
                
                // b.tophash[i] == 1
                // 后面有数据需要继续遍历判断是否当前key已经存在在map中了
                continue    // continue的作用就是继续遍历map寻找key
            }
            
            // tophash匹配成功
            //  1. 可能是hash冲突,继续向后寻找
            //  2. 可能是匹配到了key,则是更新
            
            // 8.1.2) 当前key的slot地址
            k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
            if t.indirectkey() {    // key 间接存储
                k = *((*unsafe.Pointer)(k))
            }
            // 8.1.3) 比较key和k是否相等,不相等说明 tophash冲突了
            // 如果存储的 key 是 NaN 这里比对不通过。
            if !t.key.equal(key, k) {
                continue    // key != k 继续向后寻找
            }
            
            // key == k; 匹配成功则本次是更新操作
                
            // already have a mapping for key. Update it.
            // 
            // 已经有一个key的映射。【更新它】。
            // key == key成立,但是可能生成的hash值不同,比如+0和-0
            if t.needkeyupdate() {
                typedmemmove(t.key, k, key) // 把k更新为key
            }
            // 8.1.4) 找到elem地址
            elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
            goto done
        }
        ovf := b.overflow(t)    // b后面链接的溢出桶
        // 没有溢出桶了
        if ovf == nil {
            break
        }
        b = ovf
    }
    
    // Did not find mapping for key. Allocate new cell & add entry.
    // 
    // 9) 没有找到key的映射。分配新的单元格并添加entry。新增 key/elem 

    // If we hit the max load factor or we have too many overflow buckets,
    // and we're not already in the middle of growing, start growing.
    // 
    // 10) 如果我们达到了最大负载系数,或者我们有太多溢出桶,而我们还没有达到增长的一半,就开始增长。
    // h.growing() -> (h.oldbuckets != nil); 是否正在扩容中
    if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
        hashGrow(t, h)  // 扩容初始化
        // 扩容hash table会使上面的所有操作失效,所以再试一次
        goto again // Growing the table invalidates everything, so try again
    }

    // 11) 常规桶和溢出桶(如果存在)都没找到slot,这时候需要分配新的溢出桶
    if inserti == nil {
        // The current bucket and all the overflow buckets connected to it are full, allocate a new one.
        //
        // 当前桶和连接到它的所有溢出桶已满,分配一个新的桶。
        newb := h.newoverflow(t, b) // 分配一个溢出桶 *bmap
        inserti = &newb.tophash[0]
        insertk = add(unsafe.Pointer(newb), dataOffset)
        elem = add(insertk, bucketCnt*uintptr(t.keysize))
    }

    // store new key/elem at insert position
    //
    // 12) 将新的键/elem存储在插入位置
    if t.indirectkey() {    // key 间接存储
        kmem := newobject(t.key)
        *(*unsafe.Pointer)(insertk) = kmem
        insertk = kmem
    }
    if t.indirectelem() {   // elem 间接存储
        vmem := newobject(t.elem)
        *(*unsafe.Pointer)(elem) = vmem
        // 由于done后面有 t.indirectelem() 判断所有这里没有 elem = vmem 这行代码
    }
    // 13) 拷贝数据
    typedmemmove(t.key, insertk, key) // 保存key的值
    *inserti = top // tophash 保存
    h.count++ // key/elem 对加一

done:
    // 14) 再次判断是否有其他goroutine正在对map进行写操作
    if h.flags&hashWriting == 0 {
        fatal("concurrent map writes")
    }
    h.flags &^= hashWriting // 清除hashWriting标志位
    if t.indirectelem() { // elem 间接存储
        elem = *((*unsafe.Pointer)(elem))
    }
    return elem // 返回当前slot的地址
}

isEmpty()

  1. isEmpty报告给定的tophash数组项是否表示空桶项。
1
2
3
4
// isEmpty reports whether the given tophash array entry represents an empty bucket entry.
func isEmpty(x uint8) bool {
    return x <= emptyOne    // x <= 1
}

overLoadFactor()

  1. 翻倍扩容判断条件。
1
2
3
4
5
// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
func overLoadFactor(count int, B uint8) bool {
    // count > 8 && count > 13*((1<<B) / 2)
    return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

tooManyOverflowBuckets()

  1. 等量扩容判断条件。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// tooManyOverflowBuckets reports whether noverflow buckets is too many for a map with 1<<B buckets.
// Note that most of these overflow buckets must be in sparse use;
// if use was dense, then we'd have already triggered regular map growth.
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
    // If the threshold is too low, we do extraneous work.
    // If the threshold is too high, maps that grow and shrink can hold on to lots of unused memory.
    // "too many" means (approximately) as many overflow buckets as regular buckets.
    // See incrnoverflow for more details.
    if B > 15 {
        B = 15
    }
    // The compiler doesn't see here that B < 16; mask B to generate shorter shift code.
    return noverflow >= uint16(1)<<(B&15)
}

扩容 🚀

翻倍扩容条件

  1. h.oldbuckets == nil; 未处于扩容过程中。
  2. 存储的key/elem对数量大于常规桶数量的 6.5 倍。
  3. key/elem对数量大于8
1
2
3
// 1) h.oldbuckets != nil; 处于扩容中。!h.growing():没有处于扩容中
// 2) overLoadFactor(h.count+1, h.B):新增一个元素是否需要扩容判断
if !h.growing() && overLoadFactor(h.count+1, h.B) {}

overLoadFactor()

  1. overLoadFactor 报告放置在 1<<B 桶中的项目数量是否超过 loadFactor
1
2
3
4
5
6
// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
func overLoadFactor(count int, B uint8) bool {
    // count > 8 && count > 13*( (1<<B) / 2)
    // count > 8 && count > 6.5 * (1<<B)
    return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

等量扩容条件

  1. 当常规桶(h.B)小于等于15时,溢出桶数量 大于等于 常规桶数量(1 << h.B) 就要扩容。
  2. 当常规桶(h.B)大于15时,溢出桶数量 大于等于 (1 << 15) 就要扩容了。
  3. 溢出桶的分配规则:B>=4时则分配(B-4)个溢出桶备用,因此常规桶数量大于溢出桶数量。等量扩容时备用的溢出桶一定是被用完了。
1
2
3
// 1) !h.growing():没有处于扩容中
// 2) tooManyOverflowBuckets(h.noverflow, h.B):满足等量扩容条件
if !h.growing() && tooManyOverflowBuckets(h.noverflow, h.B) {}

tooManyOverflowBuckets()

  1. tooManyOverflowBuckets 报告 noverflow 桶对于一个包含 1<<B 桶的 map 来说是否太多。
  2. 请注意,大多数溢出桶必须稀疏使用;
  3. 如果使用是密集的,那么我们就已经触发了常规的 map 增长。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// tooManyOverflowBuckets reports whether noverflow buckets is too many for a map with 1<<B buckets.
// Note that most of these overflow buckets must be in sparse use;
// if use was dense, then we'd have already triggered regular map growth.
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
    // If the threshold is too low, we do extraneous work.
    // If the threshold is too high, maps that grow and shrink can hold on to lots of unused memory.
    // "too many" means (approximately) as many overflow buckets as regular buckets.
    // See incrnoverflow for more details.
    //
    // 如果阈值太低,我们做多余的工作。
    // 如果阈值过高,则增大和缩小的映射会占用大量未使用的内存。
    // "too many"是指溢出桶的数量(大约)与普通桶相同。更多细节请参见h.incnoverflow()。
    if B > 15 {
        B = 15
    }
    // The compiler doesn't see here that B < 16; mask B to generate shorter shift code.
    // 
    // 编译器没有看到B < 16;掩码B以生成更短的移位码。
    // noverflow:表示已使用的溢出桶数量
    return noverflow >= uint16(1)<<(B&15)
}

扩容初始化

hashGrow()

  1. 扩容初始化开始。
  2. 参数:
    • t *maptype:当前 map 的类型结构。
    • h *hmap:当前 map 的内存结构。
 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
func hashGrow(t *maptype, h *hmap) {
    // If we've hit the load factor, get bigger.
    // Otherwise, there are too many overflow buckets,
    // so keep the same number of buckets and "grow" laterally.
    // 
    // 1) 如果我们达到了满载系数,就会变大。否则,溢出桶太多,所以保持相同数量的桶,横向“增长”。
    bigger := uint8(1)	// bigger是翻倍扩容还是等量扩容 1.【翻倍扩容】 0.【等量扩容】	
    if !overLoadFactor(h.count+1, h.B) {
        bigger = 0 // 标记等量扩容
        h.flags |= sameSizeGrow	// 设置等量扩容标志位
    }
    oldbuckets := h.buckets // 记录旧桶地址
    // 2) 申请一块新内存,当做新桶地址
    newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)	

    // 3) 清除iterator 和 oldIterator标志的flags
    flags := h.flags &^ (iterator | oldIterator)
    // 迭代器在扩容的前面时
    if h.flags&iterator != 0 {	// 迭代器正在运行
        flags |= oldIterator // 标记迭代旧数据
    }
    
    // 4) 设置 hmap 相关参数
    // commit the grow (atomic wrt gc)
    h.B += bigger       // 扩容后的B
    h.flags = flags     // 最新的flags状态
    h.oldbuckets = oldbuckets // 旧桶地址
    h.buckets = newbuckets  // 新桶地址
    h.nevacuate = 0	// 下一个需要迁移的旧桶号
    h.noverflow = 0 // 已使用的溢出桶

    // h.extra.overflow 和 h.extra.oldoverflow
    // 5) 溢出桶相关设置(主要是GC相关),作用在前面已经介绍了
    if h.extra != nil && h.extra.overflow != nil {
        // Promote current overflow buckets to the old generation.
        if h.extra.oldoverflow != nil {
            throw("oldoverflow is not nil")
        }
        h.extra.oldoverflow = h.extra.overflow
        h.extra.overflow = nil
    }
    // 6) 分配了备用溢出桶
    if nextOverflow != nil {
        if h.extra == nil {
            h.extra = new(mapextra)
        }
        h.extra.nextOverflow = nextOverflow
    }

    // the actual copying of the hash table data is done incrementally
    // by growWork() and evacuate().
    // 
    // 实际的哈希表数据复制是通过growWork()和evacuate()完成的。
    // 渐进式扩容分配到【写】和【删除】中调用growWork()和evacuate()完成的。
    // 注意:读操作【并没有】渐进式迁移部分数据。
}

渐进式迁移

growWork()

  1. 渐进式迁移桶数据。先迁移当前传入的桶号,再去迁移下次需要迁移的桶号
  2. 因为当前传入的桶号一定是当前正在写或删除的key对应的桶号,因此先迁移该桶,然后迁移固定增长的桶号。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func growWork(t *maptype, h *hmap, bucket uintptr) {
    // make sure we evacuate the oldbucket corresponding
    // to the bucket we're about to use
    // 
    // 确保我们清空了将要使用的桶对应的oldbucket
    evacuate(t, h, bucket&h.oldbucketmask())

    // evacuate one more oldbucket to make progress on growing
    //
    // 疏散更多的旧桶,以取得进展的增长
    if h.growing() {
        evacuate(t, h, h.nevacuate)	// 注意这里传的桶号是h.nevacuate
    }
}

evacuate()

  1. 执行一次桶迁移,在疏散旧桶时只修改了topHash,并且在没有迭代器情况下才清除key/elem
  2. 参数:
    • t *maptype:map 类型结构。
    • h *hmap:map 内存结构。
    • oldbucket uintptr:当前需要迁移桶的编号。
  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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
    // 1) oldbucket 桶号对应的 *bmap 
    b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))    // *bmap
    newbit := h.noldbuckets() // 旧桶数量,等量扩容就是 1<<h.B,翻倍扩容为 1<<(h.B-1)
    // 2) 当前桶没有被迁移,tophash[0] not in (2,3,4)
    if !evacuated(b) {
        // TODO: reuse overflow buckets instead of using new ones, if there
        // is no iterator using the old buckets.  (If !oldIterator.)
        // 
        // TODO: 如果没有迭代器使用旧的桶,则重用溢出的桶,而不是使用新的桶。(If !oldIterator)

        // xy contains the x and y (low and high) evacuation destinations.
        // 
        // xy包含x和y(low and high)疏散目的地。
        // 2.1) 记录迁移后桶的去向,x与旧桶,y则是新桶
        var xy [2]evacDst	
        x := &xy[0] // 流向旧桶时的桶信息。
        x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize)))
        x.k = add(unsafe.Pointer(x.b), dataOffset)
        x.e = add(x.k, bucketCnt*uintptr(t.keysize))

        // 2.2) 翻倍扩容
        if !h.sameSizeGrow() {	
            // Only calculate y pointers if we're growing bigger.
            // Otherwise GC can see bad pointers.
            // 
            // 只有当y指针翻倍扩容时才计算。否则GC会看到坏指针。
            y := &xy[1] // 流向新桶时的桶信息。
            y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize)))
            y.k = add(unsafe.Pointer(y.b), dataOffset)
            y.e = add(y.k, bucketCnt*uintptr(t.keysize))
        }

        // 2.3) 遍历【常规桶】和后面的【溢出桶】
        for ; b != nil; b = b.overflow(t) {
            k := add(unsafe.Pointer(b), dataOffset) // key开始首地址
            e := add(k, bucketCnt*uintptr(t.keysize)) // elem开始首地址
            // 2.4) 遍历当前桶 i、k、e 分别对应当前桶 index、key、elem
            // const bucketCnt = 8
            for i := 0; i < bucketCnt; i, k, e = i+1, add(k, uintptr(t.keysize)), add(e, uintptr(t.elemsize)) {
                top := b.tophash[i]
                // 2.4.1) 当前i处为空,top <= 1
                if isEmpty(top) { 
                    // const evacuatedEmpty = 4
                    // 0或1 标记为 4
                    b.tophash[i] = evacuatedEmpty	
                    continue
                }
                // const minTopHash = 5
                if top < minTopHash {
                    throw("bad map state")
                }
                
                // 2.4.2) 有数据需要迁移
                k2 := k	// k是当前遍历桶&key	unsafe.Pointer
                if t.indirectkey() {    // 间接存储
                    k2 = *((*unsafe.Pointer)(k2))
                }
                // 由于记录桶的流向 0.旧桶 1.新桶
                var useY uint8	
                if !h.sameSizeGrow() {  // 翻倍扩容
                    // Compute hash to make our evacuation decision (whether we need
                    // to send this key/elem to bucket x or bucket y).
                    // 
                    // 计算哈希值以做出疏散决策(我们需要将这个键/elem发送到桶x还是桶y)。
                    hash := t.hasher(k2, uintptr(h.hash0))
                    // !t.reflexivekey() == true 说明 k2 != k2 成立,比如 NaN
                    // h.flags&iterator != 0 迭代器在扩容后
                    if h.flags&iterator != 0 && !t.reflexivekey() && !t.key.equal(k2, k2) {
                        // If key != key (NaNs), then the hash could be (and probably
                        // will be) entirely different from the old hash. Moreover,
                        // it isn't reproducible. Reproducibility is required in the
                        // presence of iterators, as our evacuation decision must
                        // match whatever decision the iterator made.
                        // Fortunately, we have the freedom to send these keys either
                        // way. Also, tophash is meaningless for these kinds of keys.
                        // We let the low bit of tophash drive the evacuation decision.
                        // We recompute a new random tophash for the next level so
                        // these keys will get evenly distributed across all buckets
                        // after multiple grows.
                        // 
                        // 如果 key != key (NaNs),那么这个hash值可能(也可能会)与旧的hash值完全不同。此外,它是不可复制的。
                        // 在迭代器存在的情况下,需要重现性,因为我们的疏散决策必须匹配迭代器所做的任何决策。
                        // 幸运的是,我们可以自由地以任何方式发送这些keys。而且,tophash对于这种类型的键没有意义。(因为无法取出来)
                        // 我们让低bit位的tophash来决定疏散。
                        // 我们为下一层重新计算一个新的随机tophash值,这样这些键在增长多次后将均匀分布到所有桶中。
                        // 注意这里的top是旧桶的tophash在计算疏散方向
                        useY = top & 1  // top的最低位用来随机计算,以便均匀的分布这些key到存储桶中去
                        top = tophash(hash) // 从新计算tophash,该top存储在新桶
                    } else {
                        // 没有迭代器在运行 或 k == k成立
                        
                        // 流向新桶,newbit是旧桶数量为2的幂次方
                        if hash&newbit != 0 {	
                            useY = 1
                        }
                    }
                }

                // const evacuatedX = 2 
                // const evacuatedY = 3
                if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {
                    throw("bad evacuatedN")
                }

                // tophash = 2 或 3,数据流向; 
                // 注意上面的 useY = top & 1; 
                // 即使这里修改了旧桶的topHash也能知道疏散方向,因为2和3代表疏散方向
                b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY
                // dst目标桶
                dst := &xy[useY] // evacuation destination

                // 当前桶已经存满了
                // const bucketCnt = 8
                if dst.i == bucketCnt {
                    dst.b = h.newoverflow(t, dst.b)	// 关联桶并分配新的溢出桶
                    dst.i = 0
                    dst.k = add(unsafe.Pointer(dst.b), dataOffset)
                    dst.e = add(dst.k, bucketCnt*uintptr(t.keysize))
                }
                // tophash 迁移
                dst.b.tophash[dst.i&(bucketCnt-1)] = top // mask dst.i as an optimization, to avoid a bounds check
                if t.indirectkey() {    // 间接存储
                    *(*unsafe.Pointer)(dst.k) = k2 // copy pointer
                } else {
                    typedmemmove(t.key, dst.k, k) // copy elem
                }
                if t.indirectelem() {   // 间接存储
                    *(*unsafe.Pointer)(dst.e) = *(*unsafe.Pointer)(e)
                } else {
                    typedmemmove(t.elem, dst.e, e)
                }
                dst.i++
                // These updates might push these pointers past the end of the
                // key or elem arrays.  That's ok, as we have the overflow pointer
                // at the end of the bucket to protect against pointing past the
                // end of the bucket.
                dst.k = add(dst.k, uintptr(t.keysize))
                dst.e = add(dst.e, uintptr(t.elemsize))
            }
        }
        // Unlink the overflow buckets & clear key/elem to help GC.
        // 
        // 当前桶及溢出桶都被疏散完成后;断开溢出桶 并 清除key/elem以帮助GC
        // h.flags&oldIterator == 0:遍历器没有在遍历旧数据
        // t.bucket.ptrdata != 0:桶类型中存在指针,也就是 key/elem 类型中存在指针
        if h.flags&oldIterator == 0 && t.bucket.ptrdata != 0 {
            // 当前清除旧桶所在桶地址
            b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))
            // Preserve b.tophash because the evacuation
            // state is maintained there.
            // 
            // 保留b.tophash,因为疏散状态在那里保持。
            ptr := add(b, dataOffset) // key开始地址
            n := uintptr(t.bucketsize) - dataOffset // 需要清除的字节长度
            // 清除keys、elems、overflow帮助GC
            memclrHasPointers(ptr, n)
        }
    }

    // growWork方法在迁移了本次桶后会再迁移一次h.nevacuate,因此这里得到执行
    if oldbucket == h.nevacuate {
        advanceEvacuationMark(h, t, newbit)
    }
}

evacuated()

  1. 判断当前桶是否已被迁移。
1
2
3
4
5
6
7
func evacuated(b *bmap) bool {
    h := b.tophash[0]
    // const emptyOne = 1
    // const minTopHash = 5
    // b.tophash[0] IN (2,3,4)
    return h > emptyOne && h < minTopHash
}

type evacDst struct

  1. evacDst 是桶数据迁移流转的一个存储器。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// evacDst is an evacuation destination.
type evacDst struct {
    // 当前流转的目地桶 *bmap
    // 也就是迁移后的新桶的桶地址,比如从A桶迁移到B桶,这里就是B桶的桶地址。
    b *bmap          // current destination bucket
    // key/elem 索引到b
    // 该值默认为0,表示第一个索引下标
    i int            // key/elem index into b
    // 指向当前key存储的指针
    // 该值默认指向第一个key的地址
    k unsafe.Pointer // pointer to current key storage
    // 指向当前elem存储的指针
    // 该值默认指向第一个elem的地址
    e unsafe.Pointer // pointer to current elem storage
}

isEmpty()

  1. 判断当前 slot 处是否为空。
1
2
3
4
5
// isEmpty reports whether the given tophash array entry represents an empty bucket entry.
func isEmpty(x uint8) bool {
    // const emptyOne = 1
    return x <= emptyOne
}

advanceEvacuationMark()

  1. h.nevacuate 桶号得到迁移后相关判断迁移完毕代码。
  2. 参数:
    • h *hmap:map 的内存结构。
    • t *maptype:map 的类型结构。
    • newbit uintptr:扩容后的总数量,翻倍扩容是之前容量的两倍,等量扩容就是之前的容量。
 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
func advanceEvacuationMark(h *hmap, t *maptype, newbit uintptr) {	
    h.nevacuate++   // 下一个需要迁移的桶号
    // Experiments suggest that 1024 is overkill by at least an order of magnitude.
    // Put it in there as a safeguard anyway, to ensure O(1) behavior.
    //
    // 实验表明,1024是多余的,至少是一个数量级。把它放在那里作为一种保护措施,以确保O(1)行为。
    stop := h.nevacuate + 1024  // stop是一个循环次数最大1024次
    // newbit 未扩容前桶数量,stop > newbit 剩余扩容数量在[0,1024]间
    if stop > newbit {
        stop = newbit
    }
    // 循环一定的次数,判断后面的桶是否已经迁移了
    for h.nevacuate != stop && bucketEvacuated(t, h, h.nevacuate) {
        h.nevacuate++
    }
    // 桶已经迁移完
    if h.nevacuate == newbit { // newbit == # of oldbuckets
        // Growing is all done. Free old main bucket array.
        h.oldbuckets = nil  // 释放oldbuckets
        // Can discard old overflow buckets as well.
        // If they are still referenced by an iterator,
        // then the iterator holds a pointers to the slice.
        //
        // 可以丢弃旧的溢出桶。如果它们仍然被迭代器引用,则迭代器保存一个指向片的指针。
        if h.extra != nil {
            h.extra.oldoverflow = nil
        }
        h.flags &^= sameSizeGrow    // 清除sameSizeGrow标志位,如果设置了的话
    }
}

bucketEvacuated()

  1. 判断 bucket 桶是否已经迁移了。
1
2
3
4
func bucketEvacuated(t *maptype, h *hmap, bucket uintptr) bool {
    b := (*bmap)(add(h.oldbuckets, bucket*uintptr(t.bucketsize)))
    return evacuated(b)
}

删除 🚀

delete()

  1. 内置函数delete()将指定键值(m[key])的元素从map中删除。
  2. 如果mnil或不存在这样的元素,则delete()为空操作。
1
2
3
4
// The delete built-in function deletes the element with the specified key
// (m[key]) from the map. If m is nil or there is no such element, delete
// is a no-op.
func delete(m map[Type]Type1, key Type)

mapdelete()

  1. delete删除map
  2. 如果删除的是中间数据直接标记并清除keyelem即可。
  3. 如果删除的是最后一个则需要向前判断前面是否已被删除依次标记tophash为0。
  4. 参数:
    • t *maptype:map类型结构。
    • h *hmap:map内存结构。
    • key unsafe.Pointer:需要删除的key地址。
  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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
    if raceenabled && h != nil {
        callerpc := getcallerpc()
        pc := abi.FuncPCABIInternal(mapdelete)
        racewritepc(unsafe.Pointer(h), callerpc, pc)
        raceReadObjectPC(t.key, key, callerpc, pc)
    }
    if msanenabled && h != nil {
        msanread(key, t.key.size)
    }
    if asanenabled && h != nil {
        asanread(key, t.key.size)
    }
    // 1) 未初始的map 或 map没有key/elem对
    if h == nil || h.count == 0 {
        // 当key为any类型时,在key经过hash时可能会发生panic
        // t.hashMightPanic()判断当前hash函数是否可能发生panic
        if t.hashMightPanic() {
            // 尝试hash panic
            t.hasher(key, 0) // see issue 23734
        }
        return
    }
    // 2) 当前map正在并发写操作
    if h.flags&hashWriting != 0 {
        fatal("concurrent map writes")
    }
    // 3) key根据h.hash0生成hash值,根据上面key为any时这里hash可能会panic
    hash := t.hasher(key, uintptr(h.hash0))

    // Set hashWriting after calling t.hasher, since t.hasher may panic,
    // in which case we have not actually done a write (delete).
    // 
    // 在调用t.hasher之后设置hashWriting,因为t.hasher可能会发生错误,
    // 在这种情况下我们实际上并没有执行写入(delete)操作。
    h.flags ^= hashWriting

    bucket := hash & bucketMask(h.B)    // 当前key对应桶号
    // 4) 正在扩容进行中,去迁移该桶的数据
    if h.growing() {
        growWork(t, h, bucket)
    }
    b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))  // *bmap
    bOrig := b	// 当前变量开始桶,用于向前遍历时判断结束点
    top := tophash(hash)
search:
    // 5) 变量当前常规桶以及后面溢出桶(如果存在)
    for ; b != nil; b = b.overflow(t) {
        // 遍历当前桶 
        // const bucketCnt = 8
        for i := uintptr(0); i < bucketCnt; i++ {
            // tophash比对失败
            if b.tophash[i] != top {
                // const emptyRest = 0
                // 后面slot都没有数据
                if b.tophash[i] == emptyRest {
                    break search
                }
                continue
            }
            
            // tophash比对成功; 
            // 可能是【tophash冲突】 或 【匹配到了key】
            k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize)) // key
            k2 := k
            if t.indirectkey() {	// 间接存储
                k2 = *((*unsafe.Pointer)(k2))
            }
            // 比对失败 tophash冲突
            // NaN 的 key 无法被删除
            if !t.key.equal(key, k2) {	
                continue
            }
            
            // key 比对成功
            
            // Only clear key if there are pointers in it.
            //
            // 只有在 key 中有指针时才清除 key。
            if t.indirectkey() {
                // 间接存储时
                *(*unsafe.Pointer)(k) = nil // 清空key
            } else if t.key.ptrdata != 0 {
                // key 中存在指针类型数据时
                memclrHasPointers(k, t.key.size) // 清空key
            }
            e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize)) // elem
            if t.indirectelem() {
                // 简介存储时
                *(*unsafe.Pointer)(e) = nil // 清空elem
            } else if t.elem.ptrdata != 0 {
                // elem 中存在指针类型数据时
                memclrHasPointers(e, t.elem.size) // 清空elem
            } else {
                // elem 中不存在指针类型数据时
                memclrNoHeapPointers(e, t.elem.size) // 清空elem
            }
            // 标记 tophash 位为 1
            b.tophash[i] = emptyOne // emptyOne == 1
            // If the bucket now ends in a bunch of emptyOne states,
            // change those to emptyRest states.
            // It would be nice to make this a separate function, but
            // for loops are not currently inlineable.
            // 
            // 当前桶最后一个 key
            if i == bucketCnt-1 {
                if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
                    // 存在溢出桶并且后面还有数据,处理后续后直接返回。
                    // 因为后面有数据不需要清楚已被删除的其他slot。
                    goto notLast
                }
            } else {
                // 后面一个tophash != 0; 后面还有数据
                // const emptyRest = 0
                if b.tophash[i+1] != emptyRest {
                    goto notLast
                }
            }
            
            // 后面没有数据了,需要向前遍历处理已经删除的slot
            for {
                // const emptyRest = 0
                // 标记当前tophash为0
                b.tophash[i] = emptyRest	
                // 当前桶的第一个slot
                if i == 0 {
                    if b == bOrig {
                        // 已经到最前的常规桶了
                        break // beginning of initial bucket, we're done.
                    }
                    // Find previous bucket, continue at its last entry.
                    c := b
                    for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
                    }
                    i = bucketCnt - 1
                } else {
                    // 向前回溯
                    i--
                }
                // const emptyOne = 1
                // 因为删除的空格全部标记为了1,因此!=1说明存在数据了直接退出
                if b.tophash[i] != emptyOne {
                    break
                }
            }
        notLast:
            // key/elem对数量减一
            h.count--	
            // Reset the hash seed to make it more difficult for attackers to
            // repeatedly trigger hash collisions. See issue 25237.
            // 
            // 重置hash值,使攻击者更难触发hash碰撞。
            if h.count == 0 {
                h.hash0 = fastrand()    // 重置hash0
            }
            break search
        }
    }

    // 并发判断
    if h.flags&hashWriting == 0 {	
        fatal("concurrent map writes")
    }
    h.flags &^= hashWriting // 清除hashWriting标志位
}

len() 🚀

reflect_maplen()

  1. len() 函数实现。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//go:linkname reflect_maplen reflect.maplen
func reflect_maplen(h *hmap) int {
    if h == nil {
        return 0
    }
    if raceenabled {
        callerpc := getcallerpc()
        racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(reflect_maplen))
    }
    // 直接取值 hmap.count
    return h.count
}

迭代器 🚀

type hiter struct

  1. hash 迭代器。
  2. 如果你修改了hiter,也要修改 cmd/compile/internal/reflectdata/reflect.go 和 reflect/value.go 来匹配这个结构的布局。
 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
// A hash iteration structure.
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go
// and reflect/value.go to match the layout of this structure.
type hiter struct {
    // 必须在第一位置。写入nil来表示迭代结束(参见cmd/compile/internal/walk/range.go)。
    key         unsafe.Pointer // Must be in first position.  Write nil to indicate iteration end (see cmd/compile/internal/walk/range.go).
    // 必须在第二位置(参见cmd/compile/internal/walk/range.go)。
    elem        unsafe.Pointer // Must be in second position (see cmd/compile/internal/walk/range.go).
    // map类型结构
    t           *maptype	
    // map内存结构
    h           *hmap	
    // 常规桶地址
    buckets     unsafe.Pointer // bucket ptr at hash_iter initialization time
    // 正在迭代的桶,如果bptr == nil则从bucket获取桶号迭代
    bptr        *bmap          // current bucket
    overflow    *[]*bmap       // keeps overflow buckets of hmap.buckets alive
    oldoverflow *[]*bmap       // keeps overflow buckets of hmap.oldbuckets alive
    // 开始遍历的桶号,随机的,用于开始遍历的起点以及结束遍历的终点
    startBucket uintptr        // bucket iteration started at
    // tophash偏移值,在[0,7]中随机生成的值,用于后续 i + offset & 7 用作偏移量
    offset      uint8          // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)
    // 当前遍历已过最大桶(1 << B)时被设置为true
    wrapped     bool           // already wrapped around from end of bucket array to beginning
    B           uint8   // 初始化时桶的数量 1 << B
    // 当前桶遍历的索引,默认值从0开始,该值配合offset遍历tophash,i + offset & 7
    i           uint8
    // 初始化时是startBucket的值
    //   1. bptr == nil时bucket存储需要遍历的桶号
    //   2. bptr != nil时bucket下个桶的桶号
    bucket      uintptr // 下个迭代器迭代的桶号
    checkBucket uintptr // 需要检查的桶号
}

初始化迭代器

mapiterinit()

  1. mapiterinit() 初始化用于在 map 上进行范围搜索的 hiter 结构体。
  2. 'it'指向的hiter结构体由编译器order pass分配到栈上,或由reflect_mapiterinit分配到堆上。
  3. 两者都需要将hiter置零,因为该结构体包含指针。
  4. 参数:
    • t *maptype:map 类型结构。
    • h *hmap:map 内存结构。
    • it *hiter:遍历器存储的结构。
 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
// 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) {
    if raceenabled && h != nil {
        callerpc := getcallerpc()
        racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiterinit))
    }

    it.t = t
    // 1) 未初始化的map 或 key/elem对为空的map
    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
       // 
    // 2) 抓取桶状态快照
    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.
        //
        // 分配当前切片并记住指向当前和旧切片的指针。
        // 这使得所有相关的溢出桶都保持活跃,即使表增长,and/or 在迭代时溢出桶被添加到表中。
        h.createOverflow()	// 判断h.extra是否初始化
        it.overflow = h.extra.overflow
        it.oldoverflow = h.extra.oldoverflow
    }

    // decide where to start
    //
    // 3) 决定从哪里开始
    var r uintptr
    if h.B > 31-bucketCntBits {	// h.B > 31 - 3
        r = uintptr(fastrand64())   // uint64
    } else {
        r = uintptr(fastrand()) // uint32
    }
    // 4) 用于确定从那个桶开始遍历
    it.startBucket = r & bucketMask(h.B)    // 随机数确定开始的桶号
    // 5) 用于确定在每个桶的随机偏移量
    it.offset = uint8(r >> h.B & (bucketCnt - 1))   // 随机的偏移量[0,7]中随机数

    // iterator state
    it.bucket = it.startBucket	// 当前迭代桶号

    // Remember we have an iterator.
    // Can run concurrently with another mapiterinit().
    //
    // 6) 记住我们有一个迭代器。可以与另一个mapiterinit()并发运行。
    if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
        atomic.Or8(&h.flags, iterator|oldIterator)  // 设置迭代标志 iterator和oldIterator
    }

    mapiternext(it)
}

迭代

mapiternext()

  1. 扩容发生在迭代器之前,此时应该去变量扩容后的所有桶,都是从旧桶出发。这种情况旧桶里面可能没key/elem以及overflow信息。
  2. 扩容发生在迭代器之后,此时应该遍历所有的旧桶数量,从旧桶出发。这种情况旧桶保留了所有的key/elem以及overflow信息。
  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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
func mapiternext(it *hiter) {
    h := it.h	// *hmap
    if raceenabled {
        callerpc := getcallerpc()
        racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapiternext))
    }
    if h.flags&hashWriting != 0 {
        fatal("concurrent map iteration and map write")
    }
    t := it.t   // *maptype
    bucket := it.bucket	// 需要迭代桶号
    b := it.bptr    // 当前正在迭代的桶 *bmap
    i := it.i   // 当前遍历bptr桶的索引
    checkBucket := it.checkBucket   // 需要检查的桶

next:	
    // 根据bucket获取*bmap
    if b == nil {	
        // map遍历完了
        if bucket == it.startBucket && it.wrapped {
            // end of iteration
            // 结束迭代器
            it.key = nil
            it.elem = nil
            return
        }
        // h.growing():正在扩容中
        // it.B == h.B:扩容发生在迭代器之前,迭代器开始时已经在进行扩容了
        //  1. 这种情况可能发生部分数据还在旧桶里面,也可能在新桶里面
        // 	2. 随着扩容在进行中,可能出现部分数据在旧桶部分数据在新桶
        if h.growing() && it.B == h.B {	// 数据可能在旧桶里面,可能在新桶里
            // Iterator was started in the middle of a grow, and the grow isn't done yet.
            // If the bucket we're looking at hasn't been filled in yet (i.e. the old
            // bucket hasn't been evacuated) then we need to iterate through the old
            // bucket and only return the ones that will be migrated to this bucket.
            //
            // 迭代器是在扩容过程进行中启动的,而且扩容还没有完成
            // 如果我们查看的桶还没有被填充(即旧桶还没有被清空),那么我们需要迭代旧桶,并只返回将要迁移到此桶的那些。
            oldbucket := bucket & it.h.oldbucketmask()  // 旧桶编号
            b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))	// 旧桶 *bmap
            // 其实这种情况一直是在检查旧桶,旧桶数据有没被迁移迁移到哪里了
            if !evacuated(b) {  // 旧桶有数据,可能下面数据被迁移到新桶了
                // 需要检查新桶的桶好,因此新桶旧桶都要查看
                checkBucket = bucket	
            } else {
                // 如果旧桶没有数据则说明数据一定在新桶
                b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))  // 新桶 *bmap
                // 因此不需要再检查其他的桶了
                checkBucket = noCheck 
            }
        } else {
            // !h.growing() || h.growing() && it.B != h.B
            //  1. !h.growing(): (没有扩容)数据在b桶里,遍历b桶即可。
            //  2. h.growing() && it.B != h.B; (扩容发生在迭代之后)可能b桶被迁移了但是b桶仍然保持着key/elem;
            //  (2)这种情况我们为什么不能直接拿去b桶的elem,因为可能key又被删除或更新了需要查看新数据
            // 这里可以看出迭代数据时是按照保存快照的数据为准的。
            b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
            checkBucket = noCheck
        }
        bucket++
        // 迭代过了最大桶号
        //  1. 扩容发生在迭代之前,需要遍历所有扩容后的桶数量,被疏散的key/elem不管
        //  2. 扩容发生在迭代之后,则只会迭代旧桶数量,被疏散的key/elem去新桶里面找
        if bucket == bucketShift(it.B) {    // bucket == 1 << B
            bucket = 0	// 桶号重置为0
            it.wrapped = true   // 标记wrapped已过最大桶号
        }
        i = 0
    }
    // 迭代桶
    for ; i < bucketCnt; i++ {
        offi := (i + it.offset) & (bucketCnt - 1)
        // tophash[offi] <= 1 || tophash[offi] == 4
        // 当前slot处没有数据,因为一直遍历的是旧桶b,如果是下面这两种条件直接跳过即可
        if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty {
            // TODO: emptyRest is hard to use here, as we start iterating
            // in the middle of a bucket. It's feasible, just tricky.
            continue
        }
        k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize))
        if t.indirectkey() {    // 间接存储
            k = *((*unsafe.Pointer)(k))
        }
        e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(offi)*uintptr(t.elemsize))
        // 扩容发生在迭代器之前; 翻倍扩容才需要检查旧桶
        // checkBucket != noCheck:有需要特殊检查的桶
        // !h.sameSizeGrow():当前是翻倍扩容
        if checkBucket != noCheck && !h.sameSizeGrow() {	
            // Special case: iterator was started during a grow to a larger size
            // and the grow is not done yet. We're working on a bucket whose
            // oldbucket has not been evacuated yet. Or at least, it wasn't
            // evacuated when we started the bucket. So we're iterating
            // through the oldbucket, skipping any keys that will go
            // to the other new bucket (each oldbucket expands to two
            // buckets during a grow).
            //
            // 特殊情况:迭代器在增长到更大的尺寸期间启动,但增长还没有完成。
            // 我们正在处理一个桶,它的旧桶还没有被清空。
            // 至少,我们启动水桶时还没人撤离。因此,我们遍历oldbucket,跳过所有将进入另一个新桶的键(每个oldbucket在增长过程中会扩展到两个桶)。
            // k == k 成立,这种请款出现在 +0和-0虽然相等但是生成的hash确实不相同
            if t.reflexivekey() || t.key.equal(k, k) {	
                // If the item in the oldbucket is not destined for
                // the current new bucket in the iteration, skip it.
                //
                // 如果旧桶中的项在迭代中不是为当前的新桶指定的,则跳过它。
                hash := t.hasher(k, uintptr(h.hash0))
                // hash&bucketMask(it.B) != checkBucket; 表示当前key/elem不应该在checkBucket桶中
                // 则跳过后续到,后续会去找这种情况
                if hash&bucketMask(it.B) != checkBucket {
                    continue
                }
            } else {
                // Hash isn't repeatable if k != k (NaNs).  We need a
                // repeatable and randomish choice of which direction
                // to send NaNs during evacuation. We'll use the low
                // bit of tophash to decide which way NaNs go.
                // NOTE: this case is why we need two evacuate tophash
                // values, evacuatedX and evacuatedY, that differ in
                // their low bit.
                //
                // 如果 k != k (NaNs),则 hash 是不可重复的。
                // 在疏散过程中,我们需要一个可重复且随机的选择将NaNs派往哪个方向。
                // 我们将使用tophash的低位来决定NaNs的方向。
                // 注意:在这种情况下,我们需要两个疏散tophash值evacuatedX和evacuatedY,它们的低位不同。
                if checkBucket>>(it.B-1) != uintptr(b.tophash[offi]&1) {
                    continue	// 疏散的不是当前桶则跳过后续会去查找
                }
            }
        }
        
        // const evacuatedX = 2
        // const evacuatedY = 3
        //
        // tophash[offi] != 2或3 || k != k
        // tophash[offi] != 2或3; 存在数据
        // k != k:NaN 情况
        if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) ||
            !(t.reflexivekey() || t.key.equal(k, k)) {
            // This is the golden data, we can return it.
            // OR
            // key!=key, so the entry can't be deleted or updated, so we can just return it.
            // That's lucky for us because when key!=key we can't look it up successfully.
            //
            // 这是黄金数据,我们可以返回它。或 key!=key,因此entry不能被删除或更新,因此我们可以直接返回它。
            // 这对我们来说是幸运的,因为当 key!=key 无法成功查找时。
            it.key = k
            if t.indirectelem() {
                e = *((*unsafe.Pointer)(e))
            }
            it.elem = e
        } else {	// tophash[offi] == 2或3;数据被迁移了
            // The hash table has grown since the iterator was started.
            // The golden data for this key is now somewhere else.
            // Check the current hash table for the data.
            // This code handles the case where the key
            // has been deleted, updated, or deleted and reinserted.
            // NOTE: we need to regrab the key as it has potentially been
            // updated to an equal() but not identical key (e.g. +0.0 vs -0.0).
            //
            // 自迭代器启动以来,散列表一直在增长。
            // 这个密钥的黄金数据现在在其他地方。
            // 检查当前散列表中的数据。
            // 这段代码处理键被删除、更新或删除并重新插入的情况。
            // 注意:我们需要重新获取密钥,因为它可能已经更新到equal(),但key不相同(例如+0.0 vs -0.0)。
            rk, re := mapaccessK(t, h, k)   // 根据k查找数据
            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    // 下次需要迭代的tophash索引
        it.checkBucket = checkBucket    // 需要检查的桶号
        return
    }
    
    // 迭代b桶的溢出桶
    b = b.overflow(t)
    i = 0
    goto next
}

mapaccessK()

  1. 同时返回key和elem。由map迭代器使用
 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
// returns both key and elem. Used by map iterator
func mapaccessK(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, unsafe.Pointer) {
    if h == nil || h.count == 0 {
        return nil, nil
    }
    hash := t.hasher(key, uintptr(h.hash0))
    m := bucketMask(h.B)
    b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))    // *bmap
    if c := h.oldbuckets; c != nil {    // 正处于扩容状态
        if !h.sameSizeGrow() {  // 翻倍扩容
            // There used to be half as many buckets; mask down one more power of two.
            m >>= 1
        }
        oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
        if !evacuated(oldb) {   // 查看旧桶是否有数据
            b = oldb
        }
    }
    top := tophash(hash)
bucketloop:
    for ; b != nil; b = b.overflow(t) {
        for i := uintptr(0); i < bucketCnt; i++ {
            if b.tophash[i] != top {
                if b.tophash[i] == emptyRest {
                    break bucketloop
                }
                continue
            }
            k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
            if t.indirectkey() {
                k = *((*unsafe.Pointer)(k))
            }
            if t.key.equal(key, k) {
                e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
                if t.indirectelem() {
                    e = *((*unsafe.Pointer)(e))
                }
                return k, e
            }
        }
    }
    return nil, nil
}

不能作为map key类型

官方解释

  1. map的键可以是任何可比较的类型。
  2. 语言规范精确地定义了这一点,简而言之,可比类型是boolean, numeric, string, pointer, channel, and interface类型,以及只包含这些类型的结构体或数组。
  3. 值得注意的是,列表中没有【slices】,【 maps】, and 【functions】; 这些类型不能使用==进行比较,也不能用作映射键。
  4. 但是slicemapfunction能与nil作为比较。

slice不能作为map key

  1. slice 不能比较,因此也不能作为map key。更深层的原因应该是 Slice 不能作为map key因此Slice定义为不可比较类型。
  2. 根本原因是 slice 是不可比较类型,在GoSlice作为只是底层数组的连续的描述符,如果按照元素的比较方式Slice可以像Array一样进行比较,但是当Slice作为map key值则会出现这种情况当一个slice作为key保存在map中,我们修改当前slice的元素值会修改map中的slicekey值,这与map的存储意义相违背。因此Go干脆不支持Slice的比较,比较函数为nil。另外一方面slicecap在比较中又显得不是很重要,比如 make([]int64, 0, 10)make([]int64, 0, 9) 是否相等呢,因此Slice作为可比较类型是有歧义的。
  3. 如果Array作为由于Array是固定长度的因此比较元素即可,另外array作为map key是副本的形式不存在slice的情况。

map不能作为key

  1. map 不能比较,因此也不能作为map key。更深层的原因应该是 map 不能作为map key因此map定义为不可比较类型。
  2. 如果map能比较,那么比较 map 的所有 key/elem 对即可,但是当map作为map keykey中保存的是*hmap,因此当我们修改这个作为map keymap值也会修改到map中相应的key值,这种问题和slice类似这与map的存储意义相违背。因此Go干脆不支持Map的比较,比较函数为nil

function不能作为key

  1. function 不能比较,因此不能作为map key。更深层的原因应该是 function 不能作为map key因此function定义为不可比较类型。
  2. function类型的组成由funcval{fn uintptr}结构体以及一系列捕获列表。如果直接判断函数的签名可能存在不同的签名函数实现的函数体不同,因此函数签名不能作为判断函数相等依据,如果使用&funcval作为map key,会出现如果相同的&funcval不同的捕获列表其实并没有成功匹配到key

for range

  1. range map只是此时map的一个快照。
  2. 对于keyNaN的,for range能遍历出来。
 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
map1 := map[string]string{"one":"1", "tow":"2"}
for i, v := range map1 {
    fmt.Println(i, v)
    // original body
}

// -------------------------------
// 下面是上面编译后代码
// -------------------------------

// 定义遍历所需要的key和value变量
var i, v string					
// map_iteration_struct是一个hiter结构体,存储着map的遍历相关信息
var hiter map_iteration_struct	 
// mapiterinit 初始化map参看runtime/map.go文件
// hiter是一个哈希迭代结构,mapiternext迭代下一个哈希
for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {
    index_temp := *hiter.key
    value_temp := *hiter.val
    i = index_temp
    v = value_temp
    
    fmt.Println(i, v)
    // original body
}

map相关练习题

示例一

  1. map[string]map[string]string类型遍历。
 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
package main

import "fmt"

var mm map[string]map[string]string

func init() {
    mm = make(map[string]map[string]string, 1024)
}

func main() {
    // 测试验证
    modifyUser(mm, "one")
    modifyUser(mm, "two")
    modifyUser(mm, "three")
    fmt.Println(mm)
    modifyUser(mm, "one")
    fmt.Println(mm)
}

// 1. 使用map[string]map[string]string类型
// 2. key:表示用户名,是唯一的,不可以重复
// 3. 如果某个用户名存在,就将其密码修改"888888",如果不存在就增加这个用户信息(包括昵称nickname和密码pwd)
// 4. 编写一个函数modifyUser(users map[string]map[string]string, name string)完成上述功能

func modifyUser(user map[string]map[string]string, name string) {
    if v, ok := user[name]; ok {
        // 因为v存储的时*hmap指针
        // 因此直接修改v["pwd"]时可行的
        v["pwd"] = "888888"
        return
    }

    user[name] = map[string]string{"pwd": name, "nickname": "nickname"}
}

示例二

  1. map作为集合使用。
 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
package main

import "fmt"

// 1. map作为集合使用

var mm map[string]struct{}

func init() {
    mm = make(map[string]struct{}, 8)

    mm["redis"] = struct{}{}
    mm["mysql"] = struct{}{}
    mm["nginx"] = struct{}{}
}

func main() {
    if _, ok := mm["php"]; ok {
        // 指定元素存在
    }

    for k := range mm {
        fmt.Println(k)
    }
}