Go语言入门之Map详解

Go语言入门之Map详解

1.基础定义

map是一个无序的,k-v键值对格式的集合

(1)特点

  • 类型特点:map为引用类型,所以在函数中更新value值会永久改变
  • 顺序特点:map的遍历是无序的,因为底层是哈希表,哈希表无序
  • 初始化使用:0值或者未初始化值为nil,未初始化不可赋值使用,否则直接panic
  • 键值对:key和value总是成对出现,key是唯一的,可以是任何可比较类型
  • 动态性:可以在运行时动态地增加或删除键值对,而不需要预先声明大小
  • 快速查找:map提供了快速查找、插入和删除操作,平均时间复杂度O(1)
  • 并发:非线程安全的,保证安全需要加锁

(2)定义声明

var name map[key_type]value_type
  • name:变量名
  • key_type:键的类型
  • value_type:值的类型
// 方式一
var m map[int]string = map[int]string{}// 方式二
m := map[int]string{1 : "老一",2 : "老二",3 : "老三",
}// 方式三:5代表容量,也就是在内存中占用多大的空间,可以省略
m := make(map[int]string,5)

2.基本使用

(1)添加元素

  • 1.最常见的就是通过字面量声明map的时候进行添加,如上方式2
  • 2.其次是直接给指定键设置对应的值
mapName[key] = value// 假设map名为m,key为int,value为string
m[5] = "老五"

(2)删除元素

根据键删除元素,删除不存在的key也不会报错

delete(mapName, key)  // 假设map名为m,key为int,value为string
delete(m, 5)

(3)修改元素

修改直接修改指定键对应的值就可以

mapName[key] = newValue // 假设map名为m,key为int,value为string
m[5] = "五"

(4)获取元素

根据键获取值,ok 为是否找到的标志位,类型为布尔

如果未找到值,不会报错,会返回对应类型的空值

value, ok := mapName[key]
if !ok {fmt.Println(ok)}

(5)遍历所有元素

注意:map的遍历是无序的

for key, value := range myMap {// 处理每对键值
}// 例子
for i, s := range m {fmt.Println(i, s)}

3.底层原理

golang语言的map底层本质是用hashmap进行实现的,所以map本质上是哈希表

哈希表是一种使用哈希函数组织数据,以支持快速插入和搜索的数据结构。

哈希函数,又名散列函数,是一种将任意长度的输入(如字符串)通过特定的散列算法,变换成固定长度的输出的函数。通常会使用类似数组的形式来存储哈希值,从而保证哈希值的访问性能。

如果输入的范围超出映射输出的范围,就可能会导致不同的输入得到相同的输出,这就是哈希冲突

解决这种问题通常两种方式:开放地址法拉链法

(1)map的实现方案

开放地址法:

通常使用数组实现数据结构

  • 1.首先数组是由不同的哈希值组成,称为哈希表
  • 2.然后进行很多键,进行哈希函数确认地址放入相应位置,如果哈希表的这个槽位已经被占用,使用探测序列(如线性探测、二次探测或双重散列等)来找到下一个可用的槽位,并将冲突的键存储在那里。

缺点:

这种方法要求更多的空间来解决冲突,因为不仅要存储数据,还需要额外的空间来解决碰撞。

拉链法(go语言的map使用了该方法):

通常使用数组和链表作为底层数据结构

  • 1.首先数组是由不同的哈希值组成,称为哈希表,哈希表每个槽位都将存储一个链表
  • 2.然后会进来许多键,不同键进行hash后,求模算出hash值,链接到数组上,哈希值相同的情况(哈希碰撞),新进来的键就会挂在已有键链表的后面
  • 3.当需要查找特定键时,首先使用哈希函数确定其位置,然后在该位置的链表上进行线性搜索,直到找到匹配的键或者达到链表的末尾。

数组不同索引处链接的链表也被称之为桶(Bucket)

(2)map的底层结构

hmap
type hmap struct {count     int // 当前哈希表中的元素数量,即键值对数量,可用内置函数len()获取flags     uint8  // 标志位,标记map状态和属性的字段,如正在迭代等状态B         uint8  // 表示哈希表桶(buckets)的数量为2的B次方noverflow uint16 // 溢出桶的大致数量,扩容时会用到hash0     uint32 // 哈希种子,对key做哈希是加入种子计算哈希值,确保map安全性buckets    unsafe.Pointer // 存储桶数组的指针oldbuckets unsafe.Pointer // 扩容时用于保存旧桶数组的指针 , 大小为新桶数组的一半nevacuate  uintptr        // 扩容时的迁移进度器,迁移桶下标小于此值说明完成迁移extra *mapextra // 溢出桶的指针,指向mapextra结构体,用于存储一些额外的字段和信息
}
// mapextra 处理桶溢出的结构体
type mapextra struct {overflow    *[]*bmap    // 溢出桶数组指针,仅当key和elem非指针时才使用oldoverflow *[]*bmap    // 旧的溢出桶数组指针,仅当key和elem非指针时才使用nextOverflow *bmap      // 下一个可用的溢出桶地址
}
bmap

在源码中,bmap类型只有一个tophash字段。但在编译时期,Go编译器会根据用户代码自动注入相应的key,value等结构

表面的bmap

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 [bucketCnt]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.
}

实际的bmap

// 编译期间会动态地创建一个新的结构:
type bmap struct {topbits  [8]uint8   // 这里存储哈希值的高八位,用于在确定key的时候快速试错,加快增删改查寻址效率,有时候也叫tophashkeys     [8]keytype   // 存储key的数组,这里bmap最多存储8个键值对elems   [8]valuetype    // 存储value的数组,这里bmap也最多存储8个键值对...overflow uintptr     // 溢出桶指针
}
map底层图解

在这里插入图片描述

map的扩容

在go语言中,map的扩容是自动进行的,用于维护map的性能

首先,map在写入时会通过runtime.mapassign判断是否需要扩容

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
...// If we hit the max load factor or we have too many overflow buckets,// and we're not already in the middle of growing, start growing.if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {hashGrow(t, h)goto again // Growing the table invalidates everything, so try again}
...
}// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
func overLoadFactor(count int, B uint8) bool {return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {if B > 15 {B = 15}return noverflow >= uint16(1)<<(B&15)
}

根据上面代码判断扩容有下面两个条件:

  • 负载因子超过阈值6.5:overLoadFactor(h.count+1, h.B) , 负载因子 = 元素数量÷桶数量
  • 使用了太多溢出桶(超出32768):tooManyOverflowBuckets(h.noverflow, h.B))

扩容方式:

  • 增量扩容:

当负载因子过大时,就新建一个bucket,新的bucket长度是原来的2倍,然后旧bucket数据搬迁到新的bucket。

  • 等量扩容

数据不多,但是溢出桶太多。扩容时buckets数量不变,重新做一遍类似增量扩容的搬迁动作,把松散的键值对重新排列一次,以使bucket的使用率更高,进而保证更快的存取

扩容步骤:

  • 1.新桶数组:新建一个大小为原来两倍的新的桶数组,map会标记为扩容状态。
func hashGrow(t *maptype, h *hmap) {...// 原有桶设置给oldbucketsoldbuckets := h.buckets   // 创建新桶newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)flags := h.flags &^ (iterator | oldIterator)if h.flags&iterator != 0 {flags |= oldIterator}// commit the grow (atomic wrt gc)h.B += biggerh.flags = flagsh.oldbuckets = oldbucketsh.buckets = newbucketsh.nevacuate = 0h.noverflow = 0...
}
  • 2.重新哈希:用oldbuckets指向原来的桶数组,buckets指向新的桶数组,遍历旧的桶数组中的所有键值对,并使用哈希函数重新计算每个键的位置,将它们插入到新的桶数组中。
// 这个是mapdelete函数中的处理迁移的位置
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
...if h.growing() {// growWork(t, h, bucket)}
...
}
  • 3.逐步迁移:为了避免在扩容时暂停整个程序,Go的Map实现可能会选择渐进式驱逐进行迁移键值对。这意味着在扩容期间,旧的桶数组和新的桶数组会同时存在,新插入的键值对会直接放入新的桶中,而对旧桶的访问会触发迁移操作。
// 进入后是一个简单的判断,之后的evacuate是核心逻辑处理,特别多,感兴趣自己看源码
func growWork(t *maptype, h *hmap, bucket uintptr) {// make sure we evacuate the oldbucket corresponding// to the bucket we're about to useevacuate(t, h, bucket&h.oldbucketmask())// evacuate one more oldbucket to make progress on growingif h.growing() {evacuate(t, h, h.nevacuate)}
}
  • 4.更新内部状态:当oldbuckets中的键值对全部搬迁完毕后,Map的内部状态会更新,删除oldbuckets。

4.使用场景

  • 1.快速查找:当需要快速根据键查找值时,Map提供了平均时间复杂度为O(1)的查找性能。
  • 2.去重:当需要存储唯一键时,Map的键不允许重复,自然可以实现去重功能。
  • 3.动态集合:当需要动态地添加或删除键值对时,Map提供了灵活的操作。
  • 4.关联数据:当数据以键值对的形式存在,并且需要经常更新或查询时,Map是一个很好的选择。

5.使用建议

  1. 预分配:尽量使用make函数对已知大小的map分配容量
  2. 数据类型选择:使用较大的数据类型,如intint64
  3. 指针存值:对数据量大的结构体或者变量尽量使用指针传值存值
  4. 并发控制:对于并发访问,使用sync.Map或自行实现的并发安全Map。

6.参考资料

  • https://cloud.tencent.com/developer/article/2400014
  • https://blog.csdn.net/qq_35289736/article/details/137480760/
  • https://zhuanlan.zhihu.com/p/675715169/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/870651.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础学python(二)

1. 字典 字典的创建 最常见的创建方式&#xff1a; d {"k1": "v1", "k2": "v2"} 再向字典中添加一对键值&#xff1a; d["k3"] "v3" 字典还可以用dict()创建&#xff0c;这种方式中&#xff0c;键是不加引…

【Unity2D 2022:UI】制作主菜单

一、创建主菜单游戏场景 1. 在Scenes文件夹中新建一个游戏场景Main Menu 2. 为场景添加背景 &#xff08;1&#xff09;创建画布Canvas &#xff08;2&#xff09;在Canvas中创建新的空游戏物体Main Menu &#xff08;3&#xff09;在Main Menu中新建一个图像游戏物体Backgrou…

无人机之机身保养

一、外观检查 1、检查机器表面整洁无划痕、无针孔凹陷擦伤、畸变等损坏情况&#xff1b; 2、晃动机身&#xff0c;仔细听机身内部有无松动零件或者螺丝在机身内部。 二、桨叶检查 1、有无裂痕、磨损、变形等缺陷&#xff0c;如有明显缺陷建议更换&#xff1b; 2、卡扣、紧…

Animate软件基础:图层的基本用法

图层作为Animate软件中比较重要的功能&#xff0c;需要对其使用方法和作用理解充分&#xff0c;并熟练操作才可以更好的用来制作内容。 图层相关的功能和用法如下&#xff1a; 图层可以帮助在文档中组织插图。 可以在一个图层上绘制和编辑对象&#xff0c;而不会影响其他图层…

排座椅【详细代码题解】

[NOIP2008 普及组] 排座椅 题目描述 上课的时候总会有一些同学和前后左右的人交头接耳&#xff0c;这是令小学班主任十分头疼的一件事情。不过&#xff0c;班主任小雪发现了一些有趣的现象&#xff0c;当同学们的座次确定下来之后&#xff0c;只有有限的 D D D 对同学上课时…

USB转RS485+RS232+TTL串口电路

USB转RS485RS232TTL电路 USB转RS485RS232TTL电路如下图所示&#xff0c;可实现USB转RS485RS232TTL串口&#xff0c;一个电路模块即可实现电路调试过程中用到常用接口。 电路模块上留有2.54MM单排针接口和接线端子两种接线方式&#xff0c;可接线和跳线。电路模块同时有5V和3.3V…

开源浏览器引擎对比与适用场景:WebKit、Chrome、Gecko

WebKit与Chrome的Blink引擎对比 起源与关系&#xff1a; WebKit最初由苹果公司开发&#xff0c;用于Safari浏览器。后来&#xff0c;WebKit逐渐成为一个独立的开源项目&#xff0c;被多个浏览器厂商采用。Blink是Google基于WebKit项目分支出来的一个浏览器引擎&#xff0c;用于…

文献翻译与阅读《Integration Approaches for Heterogeneous Big Data: A Survey》

CYBERNETICS AND INFORMATION TECHNOLOGIES’24 论文原文下载地址&#xff1a;原文下载 目录 1 引言 2 大数据概述 3 大数据的异构性 4 讨论整合方法 4.1 大数据仓库&#xff08;BDW&#xff09; 4.2 大数据联盟&#xff08;BDF&#xff09; 5 DW 和 DF 方法的比较、分…

C++入门基础题:数组元素逆序(C++版互换方式)

1.题目&#xff1a; 数组元素逆置案例描述: 请声明一个5个元素的数组&#xff0c;并且将元素逆置. (如原数组元素为:1,3,2,5,4;逆置后输出结果为:4,5,2,3,1) 2.图解思路&#xff1a; 3.代码演示&#xff1a; #include<iostream>using namespace std;int main(){int a…

[k8s源码]1.client-go集群外部署

client-go是由k8s发布且维护的专门用于开发者和kubernetes交互的客户端库。它支持对k8s资源的CRUD操作&#xff08;create、read、update、delete&#xff09;&#xff0c;事件监听和处理&#xff0c;访问kubernetes集群的上下文和配置。 client go是独立于kubernetes集群之外…

Rust vs Go: 特点与应用场景分析

目录 介绍Rust的特点Go的特点Rust的应用场景Go的应用场景总结 介绍 Rust和Go&#xff08;Golang&#xff09;是现代编程语言中两个非常流行的选择。凭借各自的独特优势和广泛的应用场景&#xff0c;吸引了大量开发者的关注。本文将详细介绍Rust和Go的特点&#xff0c;并探讨它…

[Linux][Shell][Shell逻辑控制]详细讲解

目录 1.if 判断1.if-then2.if-then-else3.elif4.case5.实际上手 2.条件测试0.事前说明1.test 命令2.[]3.双括号1.(())2.[[]] 4.实际上手 3.循环1.for2.while3.until命令4.控制循环1.break2.continue 5.处理循环的输出 1.if 判断 1.if-then 语法&#xff1a;if command thenco…

大数据------JavaWeb------VueElement(完整知识点汇总)

Vue 定义 Vue是一套前端框架&#xff0c;可以免除原生JavaScript中的DOM操作&#xff0c;简化书写 之前所学的MyBatis框架是用来简化JDBC代码编写的&#xff1b;而Vue是前端框架&#xff0c;用来简化JavaScript代码编写的 在Axios与JSON综合案例的添加中有大量的DOM操作&#…

Ubuntu 22.04.4 LTS (linux) 安装 Auditd 安全审计

1 安装auditd sudo apt update sudo apt-get install auditd 2 修改配置 #sudo vim /etc/audit/auditd.conf #日志文件位置 log_file /var/log/audit/audit.log #日志文件大小(Mb) max_log_file 8 #日志文件数量 num_logs 53 启动服务 sudo systemctl restart aud…

【密码学】数字签名

一、数字签名的基本概念 数字签名是一种用于验证电子文档完整性和身份认证的密码学技术。它通过使用公钥加密体系中的私钥对文档的一部分&#xff08;通常是文档的摘要&#xff09;进行加密&#xff0c;从而创建一个“签名”。这个签名可以附在文档上&#xff0c;或作为一个单独…

如何将HEVC格式的视频转换为无损、未压缩的MP4格式视频?

在和大家分享视频格式转换之前&#xff0c;先跟大家分享一下HEVC格式的视频到底是什么文件&#xff1f;压缩原理是什么&#xff1f;了解了它的本质之后&#xff0c;我们就可以知道如何保证视频高清无损了。 如何将HEVC格式的视频转换为无损、未压缩的MP4格式视频&#xff1f; …

逐步实践复现 SELF-RAG

SELF-RAG 简介 SELF-RAG&#xff08;Self-Reflective Retrieval-Augmented Generation&#xff09;是一种检索增强生成&#xff08;RAG&#xff09;的框架&#xff0c;它通过自我反思学习检索、生成和批判&#xff0c;以提高大型语言模型&#xff08;LLM&#xff09;的质量和真…

通用业务指标管理系统设计

设计一个通用业务指标管理系统&#xff0c;旨在帮助企业全面监控和管理关键业绩指标(KPIs)&#xff0c;以驱动决策制定和业务优化。以下是一个综合性的设计方案&#xff0c;涵盖核心功能模块、技术架构、以及用户体验设计要点&#xff1a; 1. 核心功能模块 目标设定与分解 战…

「C++系列」一篇文章说透【存储类】

文章目录 一、C 存储类1. 类的定义2. 对象的创建3. 对象在内存中的布局4. 对象的存储位置 二、auto 存储类1. auto的基本用法2. auto与存储类的关系1) 自动存储类&#xff08;最常见的&#xff09;2) 静态存储类3) 动态存储类&#xff08;通过new&#xff09; 三、register 存储…

C标准库读写文件

函数介绍 库变量 变量描述size_t无符号整数类型&#xff0c;是sizeof关键字的结果&#xff0c;表示对象大小FILE文件流类型&#xff0c;适合存储文件流信息的对象类型 库宏 宏描述NULL空指针常量EOF表示已经到达文件结束的负整数stderr、stdin、stdout指向FILE类型的指针&a…