教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 go基础之map-增和改(二)

go基础之map-增和改(二)

发布时间:2022-03-08   编辑:jiaochengji.com
教程集为您提供go基础之map-增和改(二)等资源,欢迎您收藏本站,我们将为您提供最新的go基础之map-增和改(二)资源
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"/></svg>

<h3>go基础之map-增和改(二)</h3> <ul><li><ul><li>写在之前</li><li>环境说明</li><li>makemap_small和makemap的区别</li><li>添加元素(没触发扩容的情况)</li><li>一直到在发生扩容前的map内存结构是怎样的呢</li><li>发生扩容</li><li>总结</li></ul></li></ul>

<h2>写在之前</h2>

在上篇文章《go基础之map-写在前面(一)》介绍了map的数据结构,本篇会详细介绍map的增和改的代码实现,由于增和改的实现基本上差不多,所以就纳到一起分析了。如果想详细查看源码的注释,可以查看我的GitHub,欢迎批评指正。我的打算是把一些常用的数据结构都分析一遍,如果有志同道合的人,可以联系我。

<h2>环境说明</h2>

我的具体调试环境在《go基础之map-写在前面(一)》已经说明的非常仔细了,现在只讲我分析增和改的调试代码。

<pre><code class="lang-go hljs"><span class="token keyword">package</span> main <span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token string">"strconv"</span> <span class="token punctuation">)</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> m1 <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>m1<span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">20</span><span class="token punctuation">;</span> i<span class="token operator"> </span> <span class="token punctuation">{</span> str <span class="token operator">:=</span> strconv<span class="token punctuation">.</span><span class="token function">Itoa</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> m1<span class="token punctuation">[</span>str<span class="token punctuation">]</span> <span class="token operator">=</span> str <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre>

老规矩编译一波,看看第9行的申明到底干了啥?

<pre><code class="lang-bash hljs">go tool compile -N -l -S main.go <span class="token operator">></span> main.txt </code></pre>

编译结果有点多,我只列举重点代码:

<pre><code class="lang-bash hljs"><span class="token string">""</span>.main STEXT size<span class="token operator">=</span>394 args<span class="token operator">=</span>0x0 locals<span class="token operator">=</span>0x98 0x0000 00000 <span class="token punctuation">(</span>main.go:8<span class="token punctuation">)</span> TEXT <span class="token string">""</span>.main<span class="token punctuation">(</span>SB<span class="token punctuation">)</span>, ABIInternal, <span class="token variable">$152</span>-0 <span class="token punctuation">..</span><span class="token punctuation">..</span> 0x0036 00054 <span class="token punctuation">(</span>main.go:9<span class="token punctuation">)</span> LEAQ type.map<span class="token punctuation">[</span>string<span class="token punctuation">]</span>string<span class="token punctuation">(</span>SB<span class="token punctuation">)</span>, AX 0x003d 00061 <span class="token punctuation">(</span>main.go:9<span class="token punctuation">)</span> PCDATA <span class="token variable">$0</span>, <span class="token variable">$0</span> 0x003d 00061 <span class="token punctuation">(</span>main.go:9<span class="token punctuation">)</span> MOVQ AX, <span class="token punctuation">(</span>SP<span class="token punctuation">)</span> 0x0041 00065 <span class="token punctuation">(</span>main.go:9<span class="token punctuation">)</span> MOVQ <span class="token variable">$9</span>, 8<span class="token punctuation">(</span>SP<span class="token punctuation">)</span> 0x004a 00074 <span class="token punctuation">(</span>main.go:9<span class="token punctuation">)</span> MOVQ <span class="token variable">$0</span>, 16<span class="token punctuation">(</span>SP<span class="token punctuation">)</span> 0x0053 00083 <span class="token punctuation">(</span>main.go:9<span class="token punctuation">)</span> CALL runtime.makemap<span class="token punctuation">(</span>SB<span class="token punctuation">)</span> <span class="token punctuation">..</span><span class="token punctuation">..</span> 0x0107 00263 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> PCDATA <span class="token variable">$0</span>, <span class="token variable">$5</span> 0x0107 00263 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> LEAQ type.map<span class="token punctuation">[</span>string<span class="token punctuation">]</span>string<span class="token punctuation">(</span>SB<span class="token punctuation">)</span>, DX 0x010e 00270 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> PCDATA <span class="token variable">$0</span>, <span class="token variable">$4</span> 0x010e 00270 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> MOVQ DX, <span class="token punctuation">(</span>SP<span class="token punctuation">)</span> 0x0112 00274 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> PCDATA <span class="token variable">$0</span>, <span class="token variable">$6</span> 0x0112 00274 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> MOVQ <span class="token string">""</span>.m1 56<span class="token punctuation">(</span>SP<span class="token punctuation">)</span>, BX 0x0117 00279 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> PCDATA <span class="token variable">$0</span>, <span class="token variable">$4</span> 0x0117 00279 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> MOVQ BX, 8<span class="token punctuation">(</span>SP<span class="token punctuation">)</span> 0x011c 00284 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> PCDATA <span class="token variable">$0</span>, <span class="token variable">$0</span> 0x011c 00284 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> MOVQ CX, 16<span class="token punctuation">(</span>SP<span class="token punctuation">)</span> 0x0121 00289 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> MOVQ AX, 24<span class="token punctuation">(</span>SP<span class="token punctuation">)</span> 0x0126 00294 <span class="token punctuation">(</span>main.go:13<span class="token punctuation">)</span> CALL runtime.mapassign_faststr<span class="token punctuation">(</span>SB<span class="token punctuation">)</span> <span class="token punctuation">..</span><span class="token punctuation">..</span> </code></pre> <ul><li>第9行调用了<code>runtime.makemap</code>方法做一些初始化操作,我把map的初始容量设为大于8底层才会走该方法,否则会调用<code>runtime.makemap_small方法。</code></li><li>第22行调用了<code>runtime.mapassign_faststr方法</code>,该方法对应<code>main.go</code>第13行的赋值方法<code>m1[str] = str</code>。</li></ul>

我们找到了方法,在后面就可以在<code>$go_sdk_path/src/runtime/map.go</code>和<code>$go_sdk_path/src/runtime/map_faststr.go</code>
找到方法,然后断点调试即可。

<h2>
makemap_small和makemap的区别</h2>

<code>makemap_small</code>的代码如下:

<pre><code class="lang-go hljs"><span class="token comment">// makemap_small implements Go map creation for make(map[k]v) and</span> <span class="token comment">// make(map[k]v, hint) when hint is known to be at most bucketCnt</span> <span class="token comment">// at compile time and the map needs to be allocated on the heap.</span> <span class="token keyword">func</span> <span class="token function">makemap_small</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span>hmap <span class="token punctuation">{</span> h <span class="token operator">:=</span> <span class="token function">new</span><span class="token punctuation">(</span>hmap<span class="token punctuation">)</span> h<span class="token punctuation">.</span>hash0 <span class="token operator">=</span> <span class="token function">fastrand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">return</span> h <span class="token punctuation">}</span> </code></pre>

该代码的实现十分简单,就设置了一个hash种子,其他的例如申通桶内存的操作只有在真正赋值数据的时候才会创建桶。该方法在什么情况会被调用呢?如注释说说"hint is known to be at most bucketCnt at compile time and the map needs to be allocated on the heap",bucketCnt就是8个,所以上面我的示例代码为何要设初始容量为9的原因就在这里。
我就直接略过这种情况,因为在实际应用场景下还是要指定容量,避免后面因为频繁扩容造成性能损失,<code>makemap</code>的代码如下:

<pre><code class="lang-go hljs"><span class="token comment">// makemap implements Go map creation for make(map[k]v, hint).</span> <span class="token comment">// If the compiler has determined that the map or the first bucket</span> <span class="token comment">// can be created on the stack, h and/or bucket may be non-nil.</span> <span class="token comment">// If h != nil, the map can be created directly in h.</span> <span class="token comment">// If h.buckets != nil, bucket pointed to can be used as the first bucket.</span> <span class="token keyword">func</span> <span class="token function">makemap</span><span class="token punctuation">(</span>t <span class="token operator">*</span>maptype<span class="token punctuation">,</span> hint <span class="token builtin">int</span><span class="token punctuation">,</span> h <span class="token operator">*</span>hmap<span class="token punctuation">)</span> <span class="token operator">*</span>hmap <span class="token punctuation">{</span> mem<span class="token punctuation">,</span> overflow <span class="token operator">:=</span> math<span class="token punctuation">.</span><span class="token function">MulUintptr</span><span class="token punctuation">(</span><span class="token function">uintptr</span><span class="token punctuation">(</span>hint<span class="token punctuation">)</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size<span class="token punctuation">)</span> <span class="token comment">// 是否超出了最大的可分配虚拟内存或者超出了uintptr表示的值</span> <span class="token keyword">if</span> overflow <span class="token operator">||</span> mem <span class="token operator">></span> maxAlloc <span class="token punctuation">{</span> hint <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token comment">// initialize Hmap</span> <span class="token keyword">if</span> h <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> h <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>hmap<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 随机hash因子</span> h<span class="token punctuation">.</span>hash0 <span class="token operator">=</span> <span class="token function">fastrand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Find the size parameter B which will hold the requested # of elements.</span> <span class="token comment">// For hint < 0 overLoadFactor returns false since hint < bucketCnt.</span> <span class="token comment">// 计算B的值,桶的个数为 1 << B</span> B <span class="token operator">:=</span> <span class="token function">uint8</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">// 不断的循环得到最大的B值</span> <span class="token keyword">for</span> <span class="token function">overLoadFactor</span><span class="token punctuation">(</span>hint<span class="token punctuation">,</span> B<span class="token punctuation">)</span> <span class="token punctuation">{</span> B<span class="token operator"> </span> <span class="token punctuation">}</span> h<span class="token punctuation">.</span>B <span class="token operator">=</span> B <span class="token comment">// allocate initial hash table</span> <span class="token comment">// if B == 0, the buckets field is allocated lazily later (in mapassign)</span> <span class="token comment">// If hint is large zeroing this memory could take a while.</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span>B <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> nextOverflow <span class="token operator">*</span>bmap <span class="token comment">// 根据B的值去申请桶,包括逸出桶</span> h<span class="token punctuation">.</span>buckets<span class="token punctuation">,</span> nextOverflow <span class="token operator">=</span> <span class="token function">makeBucketArray</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span> <span class="token keyword">if</span> nextOverflow <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> h<span class="token punctuation">.</span>extra <span class="token operator">=</span> <span class="token function">new</span><span class="token punctuation">(</span>mapextra<span class="token punctuation">)</span> <span class="token comment">// nextOverflow指向逸出桶的内存地址</span> h<span class="token punctuation">.</span>extra<span class="token punctuation">.</span>nextOverflow <span class="token operator">=</span> nextOverflow <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> h <span class="token punctuation">}</span> </code></pre>

看程序注释大概明白该代码的作用就是得到B值和申请桶,<code>overLoadFactor</code>方法是用了6.5的扩容因子去计算出最大的B值,保证你申请的容量count要大于 (1>> B) * 6.5, 这个扩容因子想必大家都不陌生,在java中是0.75,为什么在go中是0.65呢?在<code>runtime/map.go</code>开头处有测试数据,综合考虑来说选择了6.5。大家可能注意到<code>maptype</code>用来申请桶的内存块了,下面看看<code>maptype</code>的代码,也有助于理解map的结构:

<pre><code class="lang-go hljs"><span class="token keyword">type</span> maptype <span class="token keyword">struct</span> <span class="token punctuation">{</span> typ _type <span class="token comment">// map的key的类型</span> key <span class="token operator">*</span>_type <span class="token comment">// map的value的类型</span> elem <span class="token operator">*</span>_type <span class="token comment">// 桶的类型</span> bucket <span class="token operator">*</span>_type <span class="token comment">// internal type representing a hash bucket</span> <span class="token comment">// function for hashing keys (ptr to key, seed) -> hash</span> hasher <span class="token keyword">func</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> <span class="token builtin">uintptr</span><span class="token punctuation">)</span> <span class="token builtin">uintptr</span> <span class="token comment">// key的类型的大小</span> keysize <span class="token builtin">uint8</span> <span class="token comment">// size of key slot</span> <span class="token comment">// value类型元素的大小</span> elemsize <span class="token builtin">uint8</span> <span class="token comment">// size of elem slot</span> <span class="token comment">// 桶里面所有元素的大小</span> bucketsize <span class="token builtin">uint16</span> <span class="token comment">// size of bucket</span> flags <span class="token builtin">uint32</span> <span class="token punctuation">}</span> </code></pre>

<code>makemap</code>方法里面<code>math.MulUintptr(uintptr(hint), t.bucket.size)</code>用到了bucket的size,这里这个size和<code>maptype</code>的bucketsize一模一样都是272(《go基础之map-写在前面(一)》有介绍为什么是272),所以就能计算出需要分配的内存。仔细分析<code>makemap</code>的字段,可以发现定义了map的基本数据结构,后面代码用来申请桶的内存块的时候都使用了这个数据结构。
<code>makemap</code>第36行代码调用了方法<code>makeBucketArray</code>方法来申请内存,我们简单看看它里面的细节:

<pre><code class="lang-go hljs"><span class="token comment">// makeBucketArray initializes a backing array for map buckets.</span> <span class="token comment">// 1<<b is the minimum number of buckets to allocate.</span> <span class="token comment">// dirtyalloc should either be nil or a bucket array previously</span> <span class="token comment">// allocated by makeBucketArray with the same t and b parameters.</span> <span class="token comment">// If dirtyalloc is nil a new backing array will be alloced and</span> <span class="token comment">// otherwise dirtyalloc will be cleared and reused as backing array.</span> <span class="token keyword">func</span> <span class="token function">makeBucketArray</span><span class="token punctuation">(</span>t <span class="token operator">*</span>maptype<span class="token punctuation">,</span> b <span class="token builtin">uint8</span><span class="token punctuation">,</span> dirtyalloc unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">)</span> <span class="token punctuation">(</span>buckets unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">,</span> nextOverflow <span class="token operator">*</span>bmap<span class="token punctuation">)</span> <span class="token punctuation">{</span> base <span class="token operator">:=</span> <span class="token function">bucketShift</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span> nbuckets <span class="token operator">:=</span> base <span class="token comment">// For small b, overflow buckets are unlikely.</span> <span class="token comment">// Avoid the overhead of the calculation.</span> <span class="token keyword">if</span> b <span class="token operator">>=</span> <span class="token number">4</span> <span class="token punctuation">{</span> <span class="token comment">// Add on the estimated number of overflow buckets</span> <span class="token comment">// required to insert the median number of elements</span> <span class="token comment">// used with this value of b.</span> nbuckets <span class="token operator"> =</span> <span class="token function">bucketShift</span><span class="token punctuation">(</span>b <span class="token operator">-</span> <span class="token number">4</span><span class="token punctuation">)</span> sz <span class="token operator">:=</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size <span class="token operator">*</span> nbuckets up <span class="token operator">:=</span> <span class="token function">roundupsize</span><span class="token punctuation">(</span>sz<span class="token punctuation">)</span> <span class="token keyword">if</span> up <span class="token operator">!=</span> sz <span class="token punctuation">{</span> nbuckets <span class="token operator">=</span> up <span class="token operator">/</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> dirtyalloc <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token comment">// 申请nbuckets个桶,包括了逸出桶,所以所有桶在内存中其实连续的,虽然逻辑上有差异</span> buckets <span class="token operator">=</span> <span class="token function">newarray</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucket<span class="token punctuation">,</span> <span class="token function">int</span><span class="token punctuation">(</span>nbuckets<span class="token punctuation">)</span><span class="token punctuation">)</span> b0 <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token operator">*</span>dmap<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> <span class="token function">uintptr</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">println</span><span class="token punctuation">(</span>b0<span class="token punctuation">.</span>debugOverflows<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// dirtyalloc was previously generated by</span> <span class="token comment">// the above newarray(t.bucket, int(nbuckets))</span> <span class="token comment">// but may not be empty.</span> buckets <span class="token operator">=</span> dirtyalloc size <span class="token operator">:=</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>size <span class="token operator">*</span> nbuckets <span class="token keyword">if</span> t<span class="token punctuation">.</span>bucket<span class="token punctuation">.</span>ptrdata <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">memclrHasPointers</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> size<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token function">memclrNoHeapPointers</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> size<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> base <span class="token operator">!=</span> nbuckets <span class="token punctuation">{</span> <span class="token comment">// We preallocated some overflow buckets.</span> <span class="token comment">// To keep the overhead of tracking these overflow buckets to a minimum,</span> <span class="token comment">// we use the convention that if a preallocated overflow bucket's overflow</span> <span class="token comment">// pointer is nil, then there are more available by bumping the pointer.</span> <span class="token comment">// We need a safe non-nil pointer for the last overflow bucket; just use buckets.</span> <span class="token comment">// 得到逸出桶的位置</span> nextOverflow <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">*</span>bmap<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> base<span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 得到最后一个桶的位置</span> last <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token operator">*</span>bmap<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>buckets<span class="token punctuation">,</span> <span class="token punctuation">(</span>nbuckets<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 给最后一个桶的逸出桶指针设置了桶的起始位置,环状了?官方解释说简单为了逸出桶这个指针不是空指针</span> last<span class="token punctuation">.</span><span class="token function">setoverflow</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token operator">*</span>bmap<span class="token punctuation">)</span><span class="token punctuation">(</span>buckets<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> buckets<span class="token punctuation">,</span> nextOverflow <span class="token punctuation">}</span> </code></pre>

注意12行处有个优化点,当B小于4的时候,也就是初始申请map的容量的时候的count < (1 >> B) * 6.5的时候,很大的概率其实不会使用逸出桶。当B大于4的时候,程序就预估出一个逸出桶的个数,在26行处就一并申请总的桶的内存块。第27行的代码是在源码中没有的,我只是用来调试代码所用,这个在《go基础之map-写在前面(一)》有介绍这个小技巧。在第49行就通过bucketsize计算出逸出桶的位置,并且在51到53行有个技巧,给最后一个桶的溢出桶指针设置了桶的起始地址,这个在后面系列的博客会介绍到为何这么使用。
ok,现在map的数据如下:

只有2个桶,而且每个桶的tophash值都是默认值0,由于此时key和value都为空,故没有展示出来。<code>extra</code>没有值是因为B小4没有溢出桶导致的。

<h2>
添加元素(没触发扩容的情况)</h2>

<code>m1[str] = str</code>我上面分析了是对应的<code>map_faststr.go</code>里面的<code>mapassign_faststr</code>方法。第一次添加的key和value都是string类型。

<pre><code class="lang-go hljs"><span class="token keyword">func</span> <span class="token function">mapassign_faststr</span><span class="token punctuation">(</span>t <span class="token operator">*</span>maptype<span class="token punctuation">,</span> h <span class="token operator">*</span>hmap<span class="token punctuation">,</span> s <span class="token builtin">string</span><span class="token punctuation">)</span> unsafe<span class="token punctuation">.</span>Pointer <span class="token punctuation">{</span> <span class="token comment">//d := (*dmap)(unsafe.Pointer(uintptr(h.buckets)))</span> <span class="token comment">//bucketD := uintptr(0)</span> <span class="token comment">//for bucketD < bucketShift(h.B) 3 {</span> <span class="token comment">// flag := false</span> <span class="token comment">// for _, debugKey := range d.debugKeys {</span> <span class="token comment">// if debugKey == "" {</span> <span class="token comment">// continue</span> <span class="token comment">// }</span> <span class="token comment">// if flag == false {</span> <span class="token comment">// print("bucket:")</span> <span class="token comment">// println(bucketD)</span> <span class="token comment">// }</span> <span class="token comment">// print("key:")</span> <span class="token comment">// println(debugKey)</span> <span class="token comment">// flag = true</span> <span class="token comment">// }</span> <span class="token comment">// bucketD </span> <span class="token comment">// d = (*dmap)(unsafe.Pointer(uintptr(h.buckets) bucketD*uintptr(t.bucketsize)))</span> <span class="token comment">//}</span> <span class="token comment">//println()</span> <span class="token comment">//取出第三位是否是1,如果是1则表示正有另外一个协程在往map里面写数据</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span>flags<span class="token operator">&</span>hashWriting <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"concurrent map writes"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> key <span class="token operator">:=</span> <span class="token function">stringStructOf</span><span class="token punctuation">(</span><span class="token operator">&</span>s<span class="token punctuation">)</span> <span class="token comment">//获取key的hash值</span> hash <span class="token operator">:=</span> t<span class="token punctuation">.</span><span class="token function">hasher</span><span class="token punctuation">(</span><span class="token function">noescape</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token operator">&</span>s<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">uintptr</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>hash0<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// Set hashWriting after calling t.hasher for consistency with mapassign.</span> <span class="token comment">// 将标志位设置为正在写</span> h<span class="token punctuation">.</span>flags <span class="token operator">^=</span> hashWriting <span class="token keyword">if</span> h<span class="token punctuation">.</span>buckets <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> h<span class="token punctuation">.</span>buckets <span class="token operator">=</span> <span class="token function">newobject</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucket<span class="token punctuation">)</span> <span class="token comment">// newarray(t.bucket, 1)</span> <span class="token punctuation">}</span> again<span class="token punctuation">:</span> <span class="token comment">// 获取该key落到第几个bucket,每个bucket指的是类似链并的bmap结构</span> mask <span class="token operator">:=</span> <span class="token function">bucketMask</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>B<span class="token punctuation">)</span> bucket <span class="token operator">:=</span> hash <span class="token operator">&</span> mask <span class="token comment">// 如果存在扩容情况</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span><span class="token function">growing</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 从oldbuckets里面复制到新申请的buckets里面</span> <span class="token function">growWork_faststr</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> h<span class="token punctuation">,</span> bucket<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 寻址到第几个bmap</span> b <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token operator">*</span>bmap<span class="token punctuation">)</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token function">uintptr</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>buckets<span class="token punctuation">)</span> <span class="token operator"> </span> bucket<span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 得到bmap的tophash值</span> top <span class="token operator">:=</span> <span class="token function">tophash</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span> <span class="token keyword">var</span> insertb <span class="token operator">*</span>bmap <span class="token comment">// 插入到哪个bmap里面</span> <span class="token keyword">var</span> inserti <span class="token builtin">uintptr</span> <span class="token comment">// 插入到bmap哪个位置</span> <span class="token keyword">var</span> insertk unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">// 插入key到bmap哪个位置</span> <span class="token comment">//找到一个空的地方插入该key</span> bucketloop<span class="token punctuation">:</span> <span class="token keyword">for</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token function">uintptr</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i <span class="token operator"><</span> bucketCnt<span class="token punctuation">;</span> i<span class="token operator"> </span> <span class="token punctuation">{</span> <span class="token keyword">if</span> b<span class="token punctuation">.</span>tophash<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">!=</span> top <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token function">isEmpty</span><span class="token punctuation">(</span>b<span class="token punctuation">.</span>tophash<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">&&</span> insertb <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> insertb <span class="token operator">=</span> b inserti <span class="token operator">=</span> i <span class="token punctuation">}</span> <span class="token comment">// 一开始都是0,也就是emptyRest</span> <span class="token keyword">if</span> b<span class="token punctuation">.</span>tophash<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> emptyRest <span class="token punctuation">{</span> <span class="token keyword">break</span> bucketloop <span class="token punctuation">}</span> <span class="token keyword">continue</span> <span class="token punctuation">}</span> <span class="token comment">// 到这里已经找到tophash了,2个不同的key也有可能相等,继续判断是否key相等</span> <span class="token comment">// 在bucket中的key位置</span> k <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token operator">*</span>stringStruct<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span> dataOffset<span class="token operator"> </span>i<span class="token operator">*</span><span class="token number">2</span><span class="token operator">*</span>sys<span class="token punctuation">.</span>PtrSize<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 字符串key的长度都不等的话肯定不是一个key</span> <span class="token keyword">if</span> k<span class="token punctuation">.</span><span class="token builtin">len</span> <span class="token operator">!=</span> key<span class="token punctuation">.</span><span class="token builtin">len</span> <span class="token punctuation">{</span> <span class="token keyword">continue</span> <span class="token punctuation">}</span> <span class="token comment">// 要么2个字符串直接相等,要么直接内存地址相等</span> <span class="token keyword">if</span> k<span class="token punctuation">.</span>str <span class="token operator">!=</span> key<span class="token punctuation">.</span>str <span class="token operator">&&</span> <span class="token operator">!</span><span class="token function">memequal</span><span class="token punctuation">(</span>k<span class="token punctuation">.</span>str<span class="token punctuation">,</span> key<span class="token punctuation">.</span>str<span class="token punctuation">,</span> <span class="token function">uintptr</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token builtin">len</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">continue</span> <span class="token punctuation">}</span> <span class="token comment">// already have a mapping for key. Update it.</span> <span class="token comment">// 找到了相同的key,则要去更新value</span> inserti <span class="token operator">=</span> i insertb <span class="token operator">=</span> b <span class="token keyword">goto</span> done <span class="token punctuation">}</span> <span class="token comment">// 插入第9个的时候会走向这里,但是溢出的hmap是没有的</span> ovf <span class="token operator">:=</span> b<span class="token punctuation">.</span><span class="token function">overflow</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span> <span class="token keyword">if</span> ovf <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">break</span> <span class="token punctuation">}</span> b <span class="token operator">=</span> ovf <span class="token punctuation">}</span> <span class="token comment">// Did not find mapping for key. Allocate new cell & add entry.</span> <span class="token comment">// If we hit the max load factor or we have too many overflow buckets,</span> <span class="token comment">// and we're not already in the middle of growing, start growing.</span> <span class="token comment">// 如果次数个数超出了增长因子,或者没有超出增长因子,但是有太多的逸出桶了,这个和java的hashmap一样,当太多红黑树了,还是会影响查找效率,因为理想情况下,map的</span> <span class="token comment">// 查找效率应该是o(1)</span> <span class="token keyword">if</span> <span class="token operator">!</span>h<span class="token punctuation">.</span><span class="token function">growing</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token function">overLoadFactor</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>count<span class="token operator"> </span><span class="token number">1</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">tooManyOverflowBuckets</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>noverflow<span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">hashGrow</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> h<span class="token punctuation">)</span> <span class="token keyword">goto</span> again <span class="token comment">// Growing the table invalidates everything, so try again</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> insertb <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token comment">// all current buckets are full, allocate a new one.</span> insertb <span class="token operator">=</span> h<span class="token punctuation">.</span><span class="token function">newoverflow</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> b<span class="token punctuation">)</span> inserti <span class="token operator">=</span> <span class="token number">0</span> <span class="token comment">// not necessary, but avoids needlessly spilling inserti</span> <span class="token punctuation">}</span> <span class="token comment">// 把tophash值放到topsh槽里面去</span> insertb<span class="token punctuation">.</span>tophash<span class="token punctuation">[</span>inserti<span class="token operator">&</span><span class="token punctuation">(</span>bucketCnt<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> top <span class="token comment">// mask inserti to avoid bounds checks</span> <span class="token comment">// 把key放到bmap里面</span> <span class="token comment">// dataOffset是为了得到内存对齐后的key的位置</span> <span class="token comment">// 为什么插入的是2*sys.PtrSize呢,因为string其实占了16字节</span> insertk <span class="token operator">=</span> <span class="token function">add</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>insertb<span class="token punctuation">)</span><span class="token punctuation">,</span> dataOffset<span class="token operator"> </span>inserti<span class="token operator">*</span><span class="token number">2</span><span class="token operator">*</span>sys<span class="token punctuation">.</span>PtrSize<span class="token punctuation">)</span> <span class="token comment">// store new key at insert position</span> <span class="token comment">// 这块内存就放key的值</span> <span class="token operator">*</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token operator">*</span>stringStruct<span class="token punctuation">)</span><span class="token punctuation">(</span>insertk<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token operator">*</span>key <span class="token comment">// key个数加1</span> h<span class="token punctuation">.</span>count<span class="token operator"> </span> done<span class="token punctuation">:</span> <span class="token comment">// done不关心是否是更新还是新增,拿到相应的位置即可</span> <span class="token comment">// 找到value存的内存位置</span> elem <span class="token operator">:=</span> <span class="token function">add</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>insertb<span class="token punctuation">)</span><span class="token punctuation">,</span> dataOffset<span class="token operator"> </span>bucketCnt<span class="token operator">*</span><span class="token number">2</span><span class="token operator">*</span>sys<span class="token punctuation">.</span>PtrSize<span class="token operator"> </span>inserti<span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>elemsize<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span>flags<span class="token operator">&</span>hashWriting <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"concurrent map writes"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 将标志位恢复</span> h<span class="token punctuation">.</span>flags <span class="token operator">&^=</span> hashWriting <span class="token keyword">return</span> elem <span class="token punctuation">}</span> </code></pre> <ol><li>2到21行是我的调试代码,打印下桶里面有哪些键值对。</li><li>23行和32行都是对写标志位的操作,可见,map不支持多个goroutine写操作。</li><li>26行把key转成<code>stringStructOf</code>类型,后面方便用<code>stringStructOf</code>里面的<code>len</code>和具体的string的字符数组值<code>str</code>,这也是个优化点,少了后面通过<code>len(str)</code>的计算,提高效率。</li><li>28行<code>noescape</code>防止key被逃逸分析,计算出key的hash。</li><li>38行到54行的<code>again</code>的代码块主要计算式key应该落在哪个buket,为什么作为一个代码块操作呢?是因为在触发扩容的时候,会重新计算落到哪个bucket。40行计算出bucket掩码,这里二进制值是<code>10</code>,41行和hash做与运算,得到的值就是要把key应该存的桶号。这里也是个优化操作,通过二进制运算提高效率。第50行计算得到的值正是放到bucket里面的前8个hash槽里面。先忽略掉251行的扩容情况。</li><li>57行到123行的<code>bucketloop</code>代码块主要作用是找到key和value存取的位置,并把key放到bucket所在的内存位置。里面有2个for循环,外循环轮询bucket以及bucket的逸出桶,里循环轮询桶的8个tophash槽,如果找到空的tophash槽(66行和67行)就执行到<code>done</code>语句块。71行之后就是key的高8位hash码相等了,那么就有可能bucket已经存在了这个key,所以就先比key的长度,再比较内存。102~105行先忽略。107-110会申请一个逸出桶然后把key存到逸出桶的第一个位置。113行把tophash值放到hash槽里面。</li><li>至于第66行为什么要比较hash槽等于<code>emptyRest</code>才算找到了呢?这个在后面的系列会介绍到。</li></ol>

<code>done</code>代码块的代码比较清晰,就是得到value放的内存位置,并且把状态设置为写完成。

<pre><code class="lang-go hljs">done<span class="token punctuation">:</span> <span class="token comment">// done不关心是否是更新还是新增,拿到相应的位置即可</span> <span class="token comment">// 找到value存的内存位置</span> elem <span class="token operator">:=</span> <span class="token function">add</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span>insertb<span class="token punctuation">)</span><span class="token punctuation">,</span> dataOffset<span class="token operator"> </span>bucketCnt<span class="token operator">*</span><span class="token number">2</span><span class="token operator">*</span>sys<span class="token punctuation">.</span>PtrSize<span class="token operator"> </span>inserti<span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>elemsize<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span>flags<span class="token operator">&</span>hashWriting <span class="token operator">==</span> <span class="token number">0</span> <span class="token punctuation">{</span> <span class="token function">throw</span><span class="token punctuation">(</span><span class="token string">"concurrent map writes"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 将标志位恢复</span> h<span class="token punctuation">.</span>flags <span class="token operator">&^=</span> hashWriting <span class="token keyword">return</span> elem <span class="token punctuation">}</span> </code></pre>

现在的map的内存结构是什么样的呢?

<h2>
一直到在发生扩容前的map内存结构是怎样的呢</h2>

为啥明明2个桶都没填充完就要马上扩容了呢?这是因为扩容因子作用了:

<pre><code class="lang-go hljs"><span class="token comment">// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.</span> <span class="token keyword">func</span> <span class="token function">overLoadFactor</span><span class="token punctuation">(</span>count <span class="token builtin">int</span><span class="token punctuation">,</span> B <span class="token builtin">uint8</span><span class="token punctuation">)</span> <span class="token builtin">bool</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> count <span class="token operator">></span> bucketCnt <span class="token operator">&&</span> <span class="token function">uintptr</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span> <span class="token operator">></span> loadFactorNum<span class="token operator">*</span><span class="token punctuation">(</span><span class="token function">bucketShift</span><span class="token punctuation">(</span>B<span class="token punctuation">)</span><span class="token operator">/</span>loadFactorDen<span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre>

<code>count</code>此时值是13,13 * (2 / 2) = 13,但是在下次的13的key放进来的时候就会发生扩容了。

<h2>
发生扩容</h2>

上面说到key为13的时候发生扩容,下面具体分析如何扩容的:

<pre><code class="lang-go hljs"><span class="token comment">// Did not find mapping for key. Allocate new cell & add entry.</span> <span class="token comment">// If we hit the max load factor or we have too many overflow buckets,</span> <span class="token comment">// and we're not already in the middle of growing, start growing.</span> <span class="token comment">// 如果次数个数超出了增长因子,或者没有超出增长因子,但是有太多的逸出桶了,这个和java的hashmap一样,当太多红黑树了,还是会影响查找效率,因为理想情况下,map的</span> <span class="token comment">// 查找效率应该是o(1)</span> <span class="token keyword">if</span> <span class="token operator">!</span>h<span class="token punctuation">.</span><span class="token function">growing</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span><span class="token function">overLoadFactor</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>count<span class="token operator"> </span><span class="token number">1</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">tooManyOverflowBuckets</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>noverflow<span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> d <span class="token operator">:=</span> <span class="token punctuation">(</span><span class="token operator">*</span>dmap<span class="token punctuation">)</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token function">uintptr</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>buckets<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> bucketD <span class="token operator">:=</span> <span class="token function">uintptr</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">for</span> bucketD <span class="token operator"><</span> <span class="token function">bucketShift</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>B<span class="token punctuation">)</span><span class="token operator"> </span><span class="token number">3</span> <span class="token punctuation">{</span> flag <span class="token operator">:=</span> <span class="token boolean">false</span> <span class="token keyword">for</span> i<span class="token punctuation">,</span> debugKey <span class="token operator">:=</span> <span class="token keyword">range</span> d<span class="token punctuation">.</span>debugKeys <span class="token punctuation">{</span> <span class="token keyword">if</span> debugKey <span class="token operator">==</span> <span class="token string">""</span> <span class="token punctuation">{</span> <span class="token keyword">continue</span> <span class="token punctuation">}</span> <span class="token function">println</span><span class="token punctuation">(</span>d<span class="token punctuation">.</span>tophash<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">if</span> flag <span class="token operator">==</span> <span class="token boolean">false</span> <span class="token punctuation">{</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"bucket:"</span><span class="token punctuation">)</span> <span class="token function">println</span><span class="token punctuation">(</span>bucketD<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"key:"</span><span class="token punctuation">)</span> <span class="token function">println</span><span class="token punctuation">(</span>debugKey<span class="token punctuation">)</span> flag <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> bucketD<span class="token operator"> </span> d <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">*</span>dmap<span class="token punctuation">)</span><span class="token punctuation">(</span>unsafe<span class="token punctuation">.</span><span class="token function">Pointer</span><span class="token punctuation">(</span><span class="token function">uintptr</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>buckets<span class="token punctuation">)</span> <span class="token operator"> </span> bucketD<span class="token operator">*</span><span class="token function">uintptr</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>bucketsize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token function">hashGrow</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> h<span class="token punctuation">)</span> <span class="token keyword">goto</span> again <span class="token comment">// Growing the table invalidates everything, so try again</span> <span class="token punctuation">}</span> </code></pre>

在上面的2层for循环里面虽然找到了bucket还有剩余位置,但是第7行的<code>overLoadFactor(h.count 1, h.B)</code>计算出要发生扩容。8~28行是我的调试代码,用来打印出此时map的内存结构。<code>hashGrow</code>会做具体的扩容操作,然后执行<code>again</code>从新计算落入哪个bucket。
看看<code>hashGrow</code>干了嘛:

<pre><code class="lang-go hljs"><span class="token keyword">func</span> <span class="token function">hashGrow</span><span class="token punctuation">(</span>t <span class="token operator">*</span>maptype<span class="token punctuation">,</span> h <span class="token operator">*</span>hmap<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// If we've hit the load factor, get bigger.</span> <span class="token comment">// Otherwise, there are too many overflow buckets,</span> <span class="token comment">// so keep the same number of buckets and "grow" laterally.</span> bigger <span class="token operator">:=</span> <span class="token function">uint8</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">//如果你不是因为超过了负载因子而是因为太多元素导致的,则不扩容二倍</span> <span class="token keyword">if</span> <span class="token operator">!</span><span class="token function">overLoadFactor</span><span class="token punctuation">(</span>h<span class="token punctuation">.</span>count<span class="token operator"> </span><span class="token number">1</span><span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token punctuation">)</span> <span class="token punctuation">{</span> bigger <span class="token operator">=</span> <span class="token number">0</span> h<span class="token punctuation">.</span>flags <span class="token operator">|=</span> sameSizeGrow <span class="token punctuation">}</span> oldbuckets <span class="token operator">:=</span> h<span class="token punctuation">.</span>buckets <span class="token comment">// 扩容那么就扩1倍</span> newbuckets<span class="token punctuation">,</span> nextOverflow <span class="token operator">:=</span> <span class="token function">makeBucketArray</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> h<span class="token punctuation">.</span>B<span class="token operator"> </span>bigger<span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span> flags <span class="token operator">:=</span> h<span class="token punctuation">.</span>flags <span class="token operator">&^</span> <span class="token punctuation">(</span>iterator <span class="token operator">|</span> oldIterator<span class="token punctuation">)</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span>flags<span class="token operator">&</span>iterator <span class="token operator">!=</span> <span class="token number">0</span> <span class="token punctuation">{</span> flags <span class="token operator">|=</span> oldIterator <span class="token punctuation">}</span> <span class="token comment">// commit the grow (atomic wrt gc)</span> h<span class="token punctuation">.</span>B <span class="token operator"> =</span> bigger h<span class="token punctuation">.</span>flags <span class="token operator">=</span> flags h<span class="token punctuation">.</span>oldbuckets <span class="token operator">=</span> oldbuckets h<span class="token punctuation">.</span>buckets <span class="token operator">=</span> newbuckets h<span class="token punctuation">.</span>nevacuate <span class="token operator">=</span> <span class="token number">0</span> h<span class="token punctuation">.</span>noverflow <span class="token operator">=</span> <span class="token number">0</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span>extra <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token operator">&&</span> h<span class="token punctuation">.</span>extra<span class="token punctuation">.</span>overflow <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token comment">// Promote current overflow buckets to the old generation.</span> <span class="token keyword">if</span> h<span class="token punctuation">.</span>extra Go基础编程:Map

Golang从入门到放弃200618--Map(1)Map的初始化和基本操作
Go基础学习三之数组array、切片slice、map
Go语言基础教程——map篇
应用编程基础课第三讲:Go编程基础
学习 Go 语言 1 — 基础语法
想系统学习GO语言(Golang
由浅入深聊聊Golang的map
Go编程基础-学习1
Golang map的底层实现

[关闭]
~ ~