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

go map 映射

 1、数据结构


// A header for a Go map.
type hmap struct {
	// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
	// Make sure this stays in sync with the compiler's definition.
	count     int // # live cells == size of map.  Must be first (used by len() builtin)
	flags     uint8
	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
	oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // optional fields
}

// mapextra holds fields that are not present on all maps.
type mapextra struct {
	// If both key and elem do not contain pointers and are inline, then we mark bucket
	// type as containing no pointers. This avoids scanning such maps.
	// However, bmap.overflow is a pointer. In order to keep overflow buckets
	// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.
	// overflow and oldoverflow are only used if key and elem do not contain pointers.
	// overflow contains overflow buckets for hmap.buckets.
	// oldoverflow contains overflow buckets for hmap.oldbuckets.
	// The indirection allows to store a pointer to the slice in hiter.
	overflow    *[]*bmap
	oldoverflow *[]*bmap

	// nextOverflow holds a pointer to a free overflow bucket.
	nextOverflow *bmap
}

// A bucket for a Go map.
type bmap struct {
	// tophash generally contains the top byte of the hash value
	// for each key in this bucket. If tophash[0] < minTopHash,
	// tophash[0] is a bucket evacuation state instead.
	tophash [abi.MapBucketCount]uint8
	// Followed by bucketCnt keys and then bucketCnt elems.
	// NOTE: packing all the keys together and then all the elems together makes the
	// code a bit more complicated than alternating key/elem/key/elem/... but it allows
	// us to eliminate padding which would be needed for, e.g., map[int64]int8.
	// Followed by an overflow pointer.
}

map 的底层结构包含以下几个重要的元素:

  • hmaphmap 是 Go 中 map 类型的实际数据结构,它包含了哈希表的核心信息。例如,桶的指针、大小、负载因子、哈希函数等。
  • B:2^B 代表桶的数量,初始时B=3,即桶数量为8。

  • bucketsbuckets 数组存储了 map 中的哈希桶,每个桶内存储着一部分键值对。如果哈希冲突较多,桶会存储多个键值对。

  • oldbuckets:旧桶数组

  • noverflow:代表溢出桶的数量

  • nevacuate:扩容进度,指向旧桶中某个位置

  • overflow: 为了处理大量的哈希冲突,Go 中的哈希桶通常还会有一个 overflow 字段,表示某些冲突较严重的元素会被溢出到其他位置。

 2、底层实现

(1)哈希表和桶的结构

        Go 语言的 map 底层实现是一个哈希表,通过链地址法(数组加链表)来解决冲突,它的每个单元是一个桶。它的数组是由一组桶组成,每个桶是一个链式结构,可以存储8个键值对,当发生hash冲突时首先存在桶内,如果桶满了,就在桶后面连接溢出桶来存储键值对。

(2)哈希函数

        Go 使用一个高效的哈希函数将键映射到桶的位置。不同类型的键使用不同的哈希函数,例如字符串和整数会有不同的哈希计算方法。

(3)扩容和负载因子

        负载因子为6.5,代表键值对数量与桶数量比值的上限。如果达到负载因子值或溢出桶的数量超过正常桶的数量(或者超过上限值1<<15),就会发生扩容,新的桶数量是原来的2倍数。(负载因子的计算和桶的倍数不考虑溢出桶的数量)

        Go 的 map 底层哈希表设计采用了渐进式扩容的策略,当扩容时,并不是所有的键值对都会立刻重新哈希。Go 会 分批次地、渐进地将老桶中的元素迁移到新桶中,避免在扩容过程中产生性能瓶颈。

3、常用方法

(1)创建

var mp map[string]string // nil

mp := make(map[string]string) // empty map

(2)获取元素

v := m[key],key不存在时,获取的是零值

(3)判断key是否存在

v, ok := m[key]

(4)删除元素

delete(map[key])

func example() {
    var mp1 map[string]string                 // nil
    fmt.Println(mp1, mp1 == nil, len(mp1))    // map[] true 0
 
    var mp2 = make(map[string]string)         // empty map
    fmt.Println(mp2, mp2 == nil, len(mp2))    // map[] false 0
 
    mp2["1"] = "1"
    mp2["2"] = "2"
    fmt.Println(mp2, mp2 == nil, len(mp2))    // map[1:1 2:2] false 2
 
    delete(mp2, "1")
    fmt.Println(mp2, mp2 == nil, len(mp2))    // map[2:2] false 1
}

4、使用map的注意事项

(1)键必须是可比较的类型
  • 可比较类型:键必须是可比较的类型,即支持 == 和 != 运算符。常见的可比较类型包括基本类型(如 intstringbool)、指针类型、接口类型、固定长度的数组类型以及所有字段都是可比较的结构体类型。
  • 不可比较类型:切片、映射、函数和通道类型是不可比较的,因此不能作为 map 的键。
(2)键的性能
  • 散列函数map 内部使用散列表实现,键的散列函数对性能有影响。选择合适的键类型可以提高查找和插入的效率。
  • 复杂键:如果键是复杂的结构体或大对象,计算散列值可能会比较慢,影响性能。在这种情况下,可以考虑使用更简单的键类型或优化散列函数。
(3)并发安全

  map不是并发安全的。如果多个 goroutine 同时读写同一个 map,会导致运行时错误。可以使用互斥锁(sync.Mutex 或 sync.RWMutex)来保护 map,或者使用 sync.Map 提供的并发安全版本的 map

(4)初始化和容量
  • 初始化:可以使用 make 函数初始化 map,并指定初始容量
  • 容量:初始容量可以提高性能,特别是在已知 map 大小的情况下
(5)键的默认值
  • 零值:访问 map 中不存在的键时,会返回该类型的零值。例如,对于 map[string]int,访问不存在的键会返回 0
  • 存在性检查:使用多值赋值来检查键是否存在
(6)键的内存管理
  • 指针键:使用指针作为键时,要特别注意指针的生命周期。如果指针指向的对象在 map 的生命周期内被修改或释放,可能会导致未定义行为。
  • 垃圾回收map 中的键和值都会被垃圾回收器管理。如果键或值不再被引用,垃圾回收器会自动回收它们占用的内存


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

相关文章:

  • C#发票识别、发票查验接口集成、电子发票(航空运输电子行程单)
  • matlab建模入门指导
  • Blender进阶:图像纹理节点和映射节点
  • MYSQL 库,表 基本操作
  • K8s进阶使用
  • 使用@react-three/fiber,@mkkellogg/gaussian-splats-3d加载.splat,.ply,.ksplat文件
  • c++之deque和priority_queue
  • Python注意力机制Attention下CNN-LSTM-ARIMA混合模型预测中国银行股票价格|附数据代码...
  • python cachetools 快速入门
  • RPA 机器人流程自动化
  • vue2的uniapp添加用户登录校验
  • 政企学习考试系统(源码+文档+部署+讲解)
  • HarmonyOS应用之低代码开发平台
  • Docker Compose 从入门到实战:构建现代化应用栈
  • 智能病历xml提取
  • [实用小代码java]-如何将对象存储服务器上的文件下载到客户端
  • 书生浦语XTuner 微调个人小助手
  • 深入了解Git、GitHub、GitLab及其应用技巧
  • SpringBoot中的线程安全及其处理方法
  • SQL的基本CRUD操作
  • 方法论-批判性思维提问法
  • Nginx 部署负载均衡服务全解析
  • HCIP(11)-期中综合实验(BGP、Peer、OSPF、VLAN、IP、Route-Policy)
  • 博弈连锁美业门店管理系统中如何购买课程服务?美业疗愈系统收银系统源码
  • 四期书生大模型实战营(【基础岛】- 第1关 | 书生·浦语大模型开源开放体系)
  • spring cloud 入门笔记1(RestTemplate,Consul)