当前位置: 首页 > article >正文

【Golang】Golang的Map的底层原理

文章目录

  • 前言
  • 一、介绍
  • 二、使用方式
  • 三、总结


前言

在 Golang 编程中,map 是一种常用的数据结构,用于存储键值对。map 提供了高效的查找、插入和删除操作,是开发者在日常编程中不可或缺的工具。本文将详细介绍 Golang 中 map 的底层原理,帮助读者更好地理解 map 的实现机制和使用方法。


一、介绍

1. map 的基本概念

map 是一种哈希表(Hash Table),通过键(key)来快速查找对应的值(value)。在 Golang 中,map 的定义和使用非常简单

m := make(map[string]int)
m["foo"] = 42
fmt.Println(m["foo"])  // 输出:42

2. map 的底层结构
Golang 中的 map 底层实现是一个哈希表,主要由以下几个部分组成:

  • hmap 结构: 这是 Golang 中 map 的核心数据结构,定义在 runtime/map.go 文件中。hmap 结构包含了 map 的元数据和指向 bucket 数组的指针
- type hmap struct {
    count     int            // map 中的键值对数量
    flags     uint8          // 状态标志
    B         uint8          // bucket 数组的大小是 2^B
    noverflow uint16         // 溢出 bucket 的数量
    hash0     uint32         // 哈希种子
    buckets   unsafe.Pointer // 指向 bucket 数组的指针
    oldbuckets unsafe.Pointer // 扩容时指向旧的 bucket 数组
    nevacuate uintptr        // 扩容进度
    extra     *mapextra      // 额外的字段
}
  • bucket 结构: bucket 是 map 的底层存储单元,每个 bucket 存储若干个键值对。bucket 结构包含了键值对的数组和溢出指针。
type bmap struct {
    tophash [bucketCnt]uint8 // 哈希值的高 8 位
    keys    [bucketCnt]keytype // 键数组
    values  [bucketCnt]valuetype // 值数组
    overflow *bmap // 溢出指针
}

3.哈希函数: map 使用哈希函数将键映射到特定的 bucket。Golang 使用了一个高效的哈希函数来确保键值对均匀分布在各个 bucket 中,减少冲突。

4. 冲突处理
当多个键映射到同一个 bucket 时,会发生哈希冲突。Golang 通过链地址法(chaining)和溢出桶来处理冲突。链地址法在每个 bucket 中使用链表存储冲突的键值对,而溢出桶则用于存储超过容量的键值对。

  • 链地址法: 每个 bucket 中有一个溢出指针,当 bucket 满时,新的键值对会存储在溢出 bucket 中,形成一个链表结构。
type bmap struct {
    tophash [bucketCnt]uint8 // 哈希值的高 8 位
    keys    [bucketCnt]keytype // 键数组
    values  [bucketCnt]valuetype // 值数组
    overflow *bmap // 溢出指针
}

5. 扩容机制
当 map 中的键值对数量增加到一定程度时,Golang 会自动进行扩容。扩容时,map 会创建一个新的、更大的 bucket 数组,并将原有的键值对重新哈希到新的 bucket 中。扩容可以有效减少冲突,提高查找效率。

  • 扩容条件: 当 map 中的键值对数量超过一定阈值,或者溢出 bucket 的数量过多时,会触发扩容。
  • 扩容过程: 扩容时,map 会创建一个新的 bucket 数组,大小是原来的两倍。然后,将原有的键值对重新哈希到新的 bucket 中。

二、使用方式

1. 创建和初始化 map
在 Golang 中,可以使用 make 函数创建和初始化 map:

m := make(map[string]int)
m["foo"] = 42
fmt.Println(m["foo"])  // 输出:42

也可以使用字面量初始化 map:

m := map[string]int{
    "foo": 42,
    "bar": 84,
}
fmt.Println(m["foo"])  // 输出:42

2. 插入和更新键值对
可以通过键访问 map,并进行插入和更新操作:

m := make(map[string]int)
m["foo"] = 42  // 插入键值对
m["foo"] = 84  // 更新键值对
fmt.Println(m["foo"])  // 输出:84

3. 查找键值对
可以通过键查找 map 中的值:

m := make(map[string]int)
m["foo"] = 42
value, exists := m["foo"]
if exists {
    fmt.Println(value)  // 输出:42
} else {
    fmt.Println("key not found")
}

4. 删除键值对
可以使用 delete 函数删除 map 中的键值对:

m := make(map[string]int)
m["foo"] = 42
delete(m, "foo")
value, exists := m["foo"]
if !exists {
    fmt.Println("key not found")  // 输出:key not found
}

5. 遍历 map
可以使用 range 关键字遍历 map 中的所有键值对:

m := map[string]int{
    "foo": 42,
    "bar": 84,
}
for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

三、总结

Golang 中的 map 是一种高效的哈希表数据结构,通过哈希函数和冲突处理机制,实现了快速的查找、插入和删除操作。理解 map 的底层原理有助于开发者在实际编程中更好地使用 map,并优化程序性能。希望通过本文的介绍,读者能够深入了解 Golang 中 map 的实现机制和使用方法。


http://www.kler.cn/a/385068.html

相关文章:

  • 什么时候你意识到做技术永无出路?
  • C++总结
  • A day a tweet(sixteen)——The better way of search of ChatGPT
  • 分离编译(介绍,解决“类模板定义和声明不在同一文件导致链接错误“的问题),类模板实例化原理,
  • Cursor的chat与composer的使用体验分享
  • 大模型微调技术 --> LoRA 系列之 QLoRA (省资源能手)
  • ES文档:文档操作_doc(7.9.2)
  • Webpack性能优化指南:从构建到部署的全方位策略
  • SpringBoot在城镇住房保障系统中的应用案例
  • 构建Java教学新生态:SpringBoot应用实例
  • Pyecharts使用本地文件绘制美国地图
  • 智慧商城项目-VUE2
  • 你需要了解的正则表达式相关知识
  • 前端-计算机网络
  • CSS文本样式与浮动
  • oracle 9i 使用dbms_obfuscation_toolkit加密解密
  • 蓝桥杯-Python组(py的哈希表)
  • LangChain Ollama实战文献检索助手(二)少样本提示FewShotPromptTemplate示例选择器
  • Android 手机设备的OEM-unlock解锁 和 adb push文件
  • java的threadlocal为何内存泄漏
  • 【Pytorch】model.eval()与model.train()
  • 微分段如何防止勒索软件攻击
  • 连接kafka消息队列报org.apache.kafka.clients.NetworkClient异常
  • 数据库管理-第258期 23ai:Oracle Data Redaction(20241104)
  • Android Kotlin Flow 冷流 热流
  • C++中,如何找到一个vector中最大的元素