Go语言map

map 概念

在Go语言中,map 是一种内建的数据结构,它提供了一种关联式的存储机制,允许你以键值对的形式存储数据。每个键都是唯一的,并且与一个值相关联。你可以通过键来查找、添加、更新和删除值,这类似于其他编程语言中的“字典”或“关联数组”。

Go语言中的 map 具备以下特性:

  1. 无序性map 中的键值对存储顺序并不固定,每次迭代可能会得到不同的顺序(虽然从Go 1.8开始,内部实现努力保证迭代顺序的一致性,但这并不是语言规范所保证的)。

  2. 动态大小map 的容量会在需要时自动增长,不需要预先指定大小。

  3. 键值对:键和值可以是任何类型的,但键必须是可比较的,也就是说,它不能是诸如slice、map或函数这样的不可比较类型。通常情况下,整型、浮点型、字符串、指针和结构体(其中所有字段也是可比较的)都可以作为键。

  4. 初始化map 是引用类型,所以在使用前必须初始化。可以通过 make 函数来创建一个新的空 map,例如 m := make(map[keyType]valueType)。另外,从Go 1.11开始,也可以使用类型推断来初始化 map,例如 m := map[string]int{}

  5. 操作

    • 插入m[key] = value,如果键已经存在,则更新其值;否则插入新的键值对。
    • 查找value := m[key],若键存在则获取其值,否则返回该类型的零值。
    • 删除delete(m, key),用于删除给定键对应的键值对。
    • 判断键是否存在value, ok := m[key],这里的 ok 是一个布尔值,如果键存在于 map 中,则 ok 为 true,并且返回对应的值;否则 ok 为 false,返回值为该类型的零值。
  6. 迭代:可以使用 for key, value := range m {...} 循环来遍历 map 中的所有键值对。

Go语言的 map 实现上通常采用哈希表(Hash Table),因此对于大多数操作具有接近O(1)的平均时间复杂度。需要注意的是,由于并发访问可能导致数据不一致,所以在多线程环境下,如果不采取适当的同步措施,直接操作 map 可能会引发数据竞争,这时应使用 sync.Map 或使用互斥锁来保护 map 的访问。

示例:

	//var mapname map[keytype]valuetype/*其中:mapname 为 map 的变量名。keytype 为键类型。valuetype 是键对应的值类型。*/var map1 map[string]intmap2 := map[string]int{}map3 := map[string]int{"one": 1, "two": 2}map4 := make(map[string]float32)map1 = map[string]int{"one": 1, "two": 2}map2["key1"] = 6map3["one"] = 2map4["key1"] = 3.1415926map5 := map4map5["tow"] = 5   //map是引用类型,此时map4的tow的值也变成5fmt.Println("map1:", map1) //map1: map[one:1 two:2]fmt.Println("map2:", map2) //map2: map[key1:6]fmt.Println("map3:", map3) //map3: map[one:2 two:2]fmt.Println("map4:", map4) //map4: map[key1:3.1415925 tow:5]fmt.Println("map5:", map5) //map5: map[key1:3.1415925 tow:5]

注意:如果在声明一个map并赋值,采用的是如下的方式运行的时候会报错:

var map1 map[string]int 
map1["key1"] = 5

原因是:当你声明一个map变量但未初始化它时(就像 var map1 map[string]int 这样),它的值是 nil,而不是一个空的map。尝试向一个 nil map中添加键值对会导致 panic。

map 容量

和数组不同,map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity,格式如下:

make(map[keytype]valuetype, cap)

例如:

map2 := make(map[string]float, 100)

当 map 增长到容量上限的时候,如果再增加新的 key-value,map 的大小会自动加 1,所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明。

用切片作为 map 的值

既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理 unix 机器上的所有进程,以父进程(pid 为整形)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示:

mp1 := make(map[int][]int)slice1 := []int{1, 2, 3}mp1[0] = slice1mp2 := make(map[int]*[]int)slice2 := []int{4, 5, 6}mp2[1] = &slice2// 现在你可以通过键来访问相应的切片
fmt.Println(mp1[0])  // 输出: [1 2 3]
fmt.Println(*mp2[1]) // 输出: [4 5 6]

遍历map

instances := map[string]int{"key1": 1, "key2": 2, "key3": 3}
for k, v := range instances {fmt.Println(k, v)
}//运行结果
//key3 3
//key1 1
//key2 2//如果只遍历值,可以使用如下方式
for _, v := range instances {fmt.Println(v)
}//运行结果
//1
//2
//3//如果只遍历键,可以使用如下方式
for k := range instances {fmt.Println(k)
}//运行结果
//key2
//key3
//key1

注意:遍历输出元素的顺序与填充顺序无关,不能期望 map 在遍历时返回某种期望顺序的结果。

如果需要特定顺序的遍历结果,正确的做法是先排序,代码如下:

scene := make(map[string]int)
// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
// 声明一个切片保存map数据
var sceneList []string
// 将map数据遍历复制到切片中
for k := range scene {sceneList = append(sceneList, k)
}
// 对切片进行排序
sort.Strings(sceneList) 
// 输出
fmt.Println(sceneList) // //[brazil china route]//按值排序
for _, v := range scene {sceneList = append(sceneList, v)
}
// 对切片进行排序
sort.Ints(sceneList)
// 输出
fmt.Println(sceneList) //[4 66 960]

删除map元素

使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:

delete(map, 键)

其中 map 为要删除的 map 实例,键为要删除的 map 中键值对的键。
从 map 中删除一组键值对可以通过下面的代码来完成:

map1 := make(map[string]int)// 准备map数据
map1["key1"] = 66
map1["key2"] = 4
map1["key3"] = 960delete(map1, "key1")for k, v := range map1 {
fmt.Println(k, v) 
}//key2 4
//key3 960

清空map所有元素

要清空一个Go语言中的map中的所有元素,无需逐个删除键值对,可以直接使用 make 函数重新分配一个相同类型的空映射,原映射占用的内存空间会由垃圾回收器自动回收。以下是清空映射的简单方法:

originalMap := map[string]int{"apple": 1,"banana": 2,// 更多键值对...
}// 清空映射
originalMap = make(map[string]int)fmt.Println(originalMap)  // 输出:map[]

sync.map 

sync.Map 是 Go 语言标准库 sync 包中提供的一个并发安全的映射(map)类型,特别适合在多个 Goroutine 并发读写数据的场景中使用。普通的 Go 语言 map 不支持并发安全的读写操作,如果在多线程环境中不加以同步控制,可能会导致数据竞争和不可预测的行为。

为什么要用 sync.Map:

  1. 并发安全:在高并发场景下,多个 Goroutine 同时读写 map 时,sync.Map 能够确保读写操作的线程安全性,避免因并发操作导致的数据不一致问题。
  2. 性能优化sync.Map 内部采用读写分离的设计,利用两个底层的 map 结构 (read 和 dirty) 来达到更高的并发性能。对于只读操作,sync.Map 优先从只读 map 中获取数据,只有在写操作时才会加锁,从而降低了锁竞争的概率,提高了并发性能。


下面来看下并发情况下读写 map 时会出现的问题,代码如下:

// 创建一个int到int的映射
m := make(map[int]int)
// 开启一段并发代码
go func() {// 不停地对map进行写入for {m[1] = 1}
}()
// 开启一段并发代码
go func() {// 不停地对map进行读取for {_ = m[1]}
}()
// 无限循环, 让并发程序在后台执行
for {
}

运行代码会报错,输出如下:

fatal error: concurrent map read and map write

错误信息显示,并发的 map 读和 map 写,也就是说使用了两个并发函数不断地对 map 进行读和写而发生了竞态问题,map 内部会对这种并发操作进行检查并提前发现。

需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。

sync.Map 有以下特性:

  • 无须初始化,直接声明即可。
  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
  • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。

如何使用 sync.Map:

import ("fmt""sync"
)func main() {// 创建一个 sync.Mapm := new(sync.Map)// 存储键值对m.Store("key1", "value1")m.Store("key2", "value2")// 读取值,value, ok 是两个返回值,ok 表示键是否存在value, ok := m.Load("key1")if ok {fmt.Println("Found value:", value.(string)) // 类型断言为 string 类型}// 删除键值对m.Delete("key1")// 遍历 sync.Mapm.Range(func(key, value interface{}) bool {fmt.Println("Key:", key, "Value:", value)return true // 返回 true 继续遍历,返回 false 停止遍历})
}

参考文章:

Go语言map(Go语言映射) (biancheng.net)

Go语言遍历map(访问map中的每一个键值对) (biancheng.net)

Go语言sync.Map(在并发环境中使用的map) (biancheng.net)

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

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

相关文章

MAKEFILE 从易到难

相信一个简单的makefile, 只要用过C语言的都能写出来。 但是如果工程中包含了几十个文件夹, 上万个文件, 那用一般的方式就搞不定了。 在用dpdk 的时候, 会经常修改makefile要适配我们的工程。 最开始也是用dpdk中自带的makefil…

wpf 树形结构

Simplifying the WPF TreeView by Using the ViewModel Pattern - CodeProject 【原创】WPF TreeView带连接线样式的优化(WinFrom风格) - iDream2016 - 博客园 (cnblogs.com)

Android 音视频播放器 Demo(二)—— 音频解码与音视频同步

音视频编解码系列目录: Android 音视频基础知识 Android 音视频播放器 Demo(一)—— 视频解码与渲染 Android 音视频播放器 Demo(二)—— 音频解码与音视频同步 RTMP 直播推流 Demo(一)—— 项目…

selenium截屏代码

六、截屏应用场景:失败截图,让错误看的更直观方法: driver.get_screenshot_as_file(imgepath)参数:imagepath:为图片要保存的目录地址及文件名称如: 当前目录 ./test.png上一级目录 ../test.png扩展&#x…

Qt+Ubuntu20.04:打包qt

打包程序 参考 qt项目在Linux平台上面发布成可执行程序.run_qt.run不是虚拟机的配置文件-CSDN博客 Linux下Qt程序的打包发布(1)-不使用第三方工具 - 知乎 (zhihu.com) 过程 1、Release编译 先将你的程序在release下编译通过,保证下面打包的程序是你最新的。 2…

C#调用skiasharp操作并绘制图片

之前学习ViewFaceCore时采用Panel控件和GDI将图片及识别出的人脸方框和关键点绘制出来,本文将其修改为基于SKControl和SKCanvas实现相同的显示效果并支持保存为本地图片。   新建Winform项目,在Nuget包管理器中搜索并安装一下SkiaSharp和ViewFaceCore…

【AI工具合集】图片、文本、音视频工具与A I岗位面试资料

1、AI 工具集合 全球最新热门 Al 工具, AI 工具整合包,可以下载并在 Windows 系统私有化本地化运行,包括图片、文本、视频、音频等工具资源,按照功能、业务和行业来分类。 1.1 AI 图片工具 MoneyPrinter:一键生成短…

HTTP 多个版本

了解一下各个版本的HTTP。 上个世纪90年代初期,蒂姆伯纳斯-李(Tim Berners-Lee)及其 CERN的团队共同努力,制定了互联网的基础,定义了互联网的四个构建模块: 超文本文档格式(HTML) …

Linux基础——Linux开发工具(上)_vim

前言:在了解完Linux基本指令和Linux权限后,我们有了足够了能力来学习后面的内容,但是在真正进入Linux之前,我们还得要学会使用Linux中的几个开发工具。而我们主要介绍的是以下几个: yum, vim, gcc / g, gdb, make / ma…

【初识Redis】

初识Redis Redis(Remote Dictionary Server)是一个开源的内存数据库,它提供了一个高性能的键值存储系统,并且支持多种数据结构,包括字符串、哈希、列表、集合和有序集合等。Redis的特点包括: 内存存储&…

bottom-up-attention.pytorch

环境 torch1.5cu 101cp38 on 2080ti # clone the repository inclduing Detectron2(be792b9) $ git clone --recursive https://github.com/MILVLG/bottom-up-attention.pytorch$ cd detectron2 $ pip install -e . $ cd .. detectron2直接克隆有问题,需要把det…

C语言实验-数组、字符串以及指针

一&#xff1a; 求一个NN矩阵主、次对角线上所有元素之和。矩阵输入、矩阵输出、矩阵对角线求和分别用三个子函数实现。&#xff08;N的值由用户从键盘输入&#xff09; #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h>void print(int(*arr…

有哪些好用的局域网电脑监控系统软件?

企业员工不好管理&#xff1f;&#xff1f;&#xff1f; 局域网已成为企业日常运营不可或缺的一部分。 然而&#xff0c;随着网络技术的普及&#xff0c;员工在局域网中的不当行为也日益增多&#xff0c;如滥用网络资源、泄露敏感信息、消极怠工等。 为了解决这些问题&#x…

植物大战僵尸杂交版

1.感谢作者潜艇伟伟迷 2.大小大概110M&#xff0c;下载链接在下方 链接&#xff1a;https://pan.baidu.com/s/1Ew6iTg0_d_Ut8N9_18KGLw 提取码&#xff1a;yspa 3.祝大家玩的开心

嵌入式学习——C语言基础——day13

1. 结构体类型的定义 struct 类型名 { 数据类型1 成员变量1; 数据类型2 成员变量2; 数据类型3 成员变量3; ... }; 定义结构体中可以使用的数据类型有 1.基本数据类型&#xff1a;int long short char doub…

C++-10

1.C一个程序&#xff0c;实现两个类&#xff0c;分别存放输入的字符串中的数字和字母&#xff0c;并按各自的顺序排列&#xff0c; 类中实现-一个dump函数&#xff0c;调C用后输出类中当前存放的字符串结果。 例如&#xff0c;输入1u4y2a3d,输出:存放字母的类&#xff0c;输出a…

Mybatis-plus对单表操作的封装

MyBatis-Plus单表操作详解及拓展 MyBatis-Plus是一个基于MyBatis的增强工具&#xff0c;它提供了丰富的CRUD操作和分页查询等功能&#xff0c;极大地简化了开发人员的数据库操作。本文将详细介绍MyBatis-Plus官方已经写好的单表操作&#xff0c;并提供一些拓展内容。 1. 引言…

爬虫 - 基于requests进行二次开发

项目地址 https://github.com/markadc/wauo.git持续更新中…

树,二叉树的基本概念介绍,二叉树的性质

目录 树 树的定义 树的相关概念 树的存储结构 树在实际中的运用&#xff08;表示文件系统的目录树结构 &#xff09; 二叉树 二叉树的定义 现实中的二叉树 二叉树的特点 特殊的二叉树 1.斜树 2.满二叉树 3.完全二叉树 二叉树的性质 性质1&#xff1a;二叉树的第…

嵌入式C语言教程:实现DMA控制的高速SPI通信

在高速数据传输应用中&#xff0c;SPI&#xff08;串行外设接口&#xff09;是一种常用的通信协议。 利用DMA&#xff08;直接内存访问&#xff09;进行SPI数据传输可以显著提高数据处理效率&#xff0c;减少CPU的负载。 本文将详细介绍如何在STM32微控制器上配置和使用DMA来…