Golang map 如何扩容
Golang map 如何扩容
-
双倍扩容:扩容采取了一种称为“渐进式 ”的方式,原有的 key 并不会一 次性搬迁完毕,每次最多只会搬迁 2 个 bucket。
-
等量扩容:重新排列,极端情况下,重新排列也解决不了,map 存储就会蜕 变成链表,性能大大降低,此时哈希因子hash0 的设置,可以降低此类极 端场景的发生。
在Go语言中,map
的内部结构是基于哈希表的,它会自动根据需要扩容以存储更多的键值对,而不需要开发者手动干预。但是,了解map
是如何扩容的以及扩容的时机对于优化程序性能和理解Go的内部机制是有帮助的。
扩容机制
Go的map
在内部使用一个数组来存储哈希桶(buckets),每个哈希桶可以存储多个键值对。当map
中的元素数量超过其当前容量的加载因子(默认为6.5)时,map
会自动扩容。扩容通常是将现有的buckets数量乘以2(在Go 1.18及以前版本中),并在新的buckets数组中重新分配键值对。从Go 1.18开始,引入了更复杂的扩容策略,旨在优化内存使用和性能。
示例代码
由于map
的扩容是自动的,你不能直接控制或触发它,但可以通过添加元素来间接观察其行为。下面是一个简单的示例,演示了如何向map
中添加元素,并观察扩容可能导致的内存分配变化(通过运行时统计信息)。
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
m := make(map[int]int)
// 初始时,map占用的内存
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
initialAlloc := ms.Alloc
// 填充map以触发扩容
for i := 0; i < 100000; i++ {
m[i] = i
}
// 再次获取内存分配情况
runtime.ReadMemStats(&ms)
finalAlloc := ms.Alloc
fmt.Printf("Initial allocation: %d bytes\n", initialAlloc)
fmt.Printf("Final allocation: %d bytes\n", finalAlloc)
fmt.Printf("Increase in allocation: %d bytes\n", finalAlloc-initialAlloc)
// 注意:实际增加的内存量会因运行时环境和map内部实现细节而异
// 为了演示,我们可以暂停一会儿,观察内存变化(在实际应用中,这通常不是必需的)
time.Sleep(1 * time.Second)
}
注意:这个示例主要是为了展示如何观察map
扩容可能导致的内存分配变化,而不是直接控制或触发扩容。在Go中,map
的扩容是自动和内部处理的,你不需要(也不能)直接控制它。
结论
虽然不能直接控制map
的扩容,但了解它的扩容机制可以帮助你更好地理解Go的内存使用和性能优化。当设计使用大量键值对的程序时,考虑map
的扩容行为是一个好习惯。