定义
概念定义
映射(Map)是一种用于存储键值对的数据结构。它类似于其他编程语言中的哈希表或字典。Go的映射使得我们可以快速地根据键查找、插入和删除值。
代码定义
package main
import "fmt"
func main() {
// 定义并初始化映射
m := map[string]int{
"Alice": 25,
"Bob": 30,
}
// 插入或更新元素
m["Charlie"] = 35
// 访问元素
age := m["Alice"]
fmt.Println("Alice's age is", age)
// 删除元素
delete(m, "Bob")
// 检查键是否存在
if age, exists := m["Bob"]; exists {
fmt.Println("Bob's age is", age)
} else {
fmt.Println("Bob is not in the map")
}
// 遍历映射
for key, value := range m {
fmt.Println(key, value)
}
}
----------------运行结果----------------------
Alice's age is 25
Bob is not in the map
Alice 25
Charlie 35
使用场景
主要用于KV存储,快速的查找、插入、删除数据。
特性
- 无序:映射中的键值对是无序的,遍历时顺序不固定。
- 动态大小:映射的大小是动态变化的,不需要预定义大小。
- 键的类型要求:键的类型必须是可比较的,不能使用切片、映射和函数作为键。
问题探究
映射的底层存储结构是怎样的
基础结构
-
哈希表(Hash Table):
哈希表是一个包含桶数组和元数据的结构。元数据包括哈希表的大小、负载因子等信息。
-
桶(Bucket):
每个桶是一个小数组,存储若干个键值对(通常是8个)。桶中还包含指向溢出桶的指针,用于处理哈希冲突。
哈希表工作原理
-
哈希函数:
哈希函数将键转换为一个哈希值(hash value),然后根据哈希值计算出桶的索引。
-
插入元素:
通过哈希函数计算出桶的索引,将键值对插入到相应的桶中。如果桶已满,则创建一个溢出桶,将新的键值对插入溢出桶。
-
查找元素:
通过哈希函数计算出桶的索引,然后在桶中查找对应的键。如果在第一个桶中没有找到键,则检查溢出桶,直到找到对应的键或所有桶都被检查完。 -
删除元素:
通过哈希函数计算出桶的索引,在桶中查找到对应的键,并将其删除。如果有溢出桶,也需要在溢出桶中查找并删除对应的键。 -
扩展和收缩:
当哈希表的负载因子超过某个阈值时,哈希表会扩展,将桶数组的大小加倍。扩展过程中,旧的桶数组中的元素会被重新哈希并搬迁到新的桶数组中。类似地,当负载因子过低时,哈希表会收缩,减少桶数组的大小。
映射是如何动态改变大小的
当映射中的元素数量变化时,Go的哈希表会进行动态调整,以保持操作的高效性。调整大小通常涉及重新哈希和重新分配桶数组。具体过程如下:
-
初始分配:当映射第一次被创建时,会分配一个初始大小的桶数组。这个大小通常是一个小的固定值,比如8。
-
负载因子和再散列:负载因子(Load Factor)是哈希表中元素数量与桶数量的比值。当负载因子超过某个阈值(例如0.75),意味着哈希表变得过于拥挤,性能可能下降,此时需要扩展哈希表。
-
扩展哈希表:当哈希表需要扩展时,通常会将桶数组的大小加倍,并重新分配新的桶数组。然后,所有的键值对将被重新哈希并插入到新的桶数组中。这一过程称为再散列(Rehashing)。
-
缩小哈希表:类似地,当映射中的元素数量减少,负载因子低于某个阈值时,哈希表也可能会缩小,减少桶数组的大小,以节省内存。
此处没代码,因为目前go不支持查看映射的大小,所以没法使用代码验证