golang map的底层实现原理

在Go语言中,`map`是一种非常常用的数据结构,用于存储键值对。`map`的底层实现是一个哈希表(hash table),这使得`map`具有快速的查找、插入和删除操作。

哈希表(Hash Table)

哈希表是一种数据结构,它通过将键映射到一个固定大小的数组中来实现快速查找。在Go语言中,`map`的底层实现就是一个哈希表。当我们向`map`中插入键值对时,Go语言会根据键计算出一个哈希值,然后将该键值对存储在哈希表的相应位置。

冲突处理

由于哈希表的数组大小是有限的,不同的键可能会映射到相同的位置,这就是哈希冲突。Go语言使用开放寻址法(open addressing)来处理哈希冲突,具体来说是线性探测法(linear probing)。如果发生冲突,就会线性地探测下一个位置,直到找到一个空闲的位置来存储键值对。

动态扩容

为了避免哈希表过度填充而影响性能,Go语言的`map`在填充程度达到一定阈值时会自动进行动态扩容。在扩容过程中,Go语言会重新分配一个更大的哈希表,并将原有的键值对重新计算哈希值后插入新的哈希表中。这样可以保持哈希表的填充程度在一个合理的范围内,保证查找、插入和删除操作的性能。

并发安全

在Go语言中,`map`是并发安全的,多个goroutine可以同时对`map`进行读取操作,但同时只能有一个goroutine进行写入操作。这是通过在底层实现中使用读写锁(RWMutex)来实现的。

总之,`map`作为一种常用的数据结构,在Go语言中通过哈希表的形式实现,具有快速的查找、插入和删除操作,同时通过动态扩容和并发安全机制保证了其性能和安全性。

数据结构

在Go语言中,`map`的数据结构是由`hmap`结构体表示的。`hmap`结构体定义在`runtime/map.go`文件中,是Go语言中`map`的底层数据结构。

下面是`hmap`结构体的定义:

type hmap struct {
count int // 当前map中的元素个数
flags uint8
B uint8 // 稀疏数组的桶的数量
noverflow uint16
hash0 uint32 // 随机种子,用于计算哈希值
buckets unsafe.Pointer // 指向存储桶数组的指针
oldbuckets unsafe.Pointer // 用于扩容时临时存储旧的桶数组
nevacuate uintptr // 扩容时的标记
extra *mapextra // 用于存储overflow指针等信息
}

 

  • - `count`:当前`map`中的元素个数。
  • - `flags`:标志位,用于标记`map`的状态。
  • - `B`:稀疏数组的桶的数量,实际桶的数量为2的`B`次方。
  • - `noverflow`:溢出桶的数量。
  • - `hash0`:随机种子,用于计算哈希值。
  • - `buckets`:指向存储桶数组的指针,存储着键值对。
  • - `oldbuckets`:在扩容时用于临时存储旧的桶数组。
  • - `nevacuate`:在扩容时的标记。
  • - `extra`:指向`mapextra`结构体的指针,用于存储一些额外信息。

除了`hmap`结构体外,还有一个`mapextra`结构体用于存储一些额外的信息,例如溢出桶的指针等。

type mapextra struct {
overflow *[]*bmap // 指向溢出桶的指针数组
oldoverflow *[]*bmap // 用于扩容时临时存储旧的溢出桶
}

 

在`map`的实现中,键值对会存储在桶(bucket)中,每个桶可以存储若干个键值对。如果一个桶中的键值对数量过多,会导致哈希冲突,此时会使用溢出桶(overflow bucket)来存储冲突的键值对。

总的来说,`map`的数据结构是由`hmap`和`mapextra`两个结构体组成,通过这些结构体的组合实现了`map`的功能,包括快速查找、插入和删除操作,动态扩容以及并发安全性。

posted @ 2024-06-18 22:00  pebblecome  阅读(4)  评论(0)    收藏  举报