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

【go语言】map 和 list

一、map

map 是一种无序的键值对的集合

  • 无序 :map[key]
  • 键值对:key - value 

       map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。map 是一种集合,所以我们可以像迭代数组和切片那样迭代他。不过,map 是无序的,我们无法决定他的返回顺序。最后 map 也是引用类型。

1.1 map 的定义

package mainimport "fmt"// map 集合,保存数据的一种结构
func main() {// 创建一个map,也是一个变量,数据类型是 map// map[key]valuevar map1 map[int]string // 只是声明了但是没有初始化,不能使用 nilif map1 == nil {fmt.Println("map1==nil")}// 更多的时候使用的是make方法创建var map2 = make(map[string]string) // 创建了mapfmt.Println(map1)fmt.Println(map2)// 在创建的时候,添加一些基础数据// map[string]int nil// map[string]int {key:value,key:value,......}var map3 = map[string]int{"Go": 100, "Java": 10, "C": 60}fmt.Println(map3) // 关于map的类型,就如定义的一般 map[string]int// 类型主要是传参要确定fmt.Printf("%T\n", map3)
}

1.2 map 的使用

  • 创建并初始化 map
  • map[key] = value,将 value 赋值给对应的 map 的 key
  • 判断 key 是否存在,value, ok = map[key]
  • 删除 map 中的元素,delete(map, key)
  • 新增 map[key] = value
  • 修改 map[key] = value,如果存在这个 key 就是修改
  • 查看 map 的大小,len(map) 

1.3 map 的初始化和赋值

       在 Go 语言中,map 是一种无序的集合类型,用来存储键值对(key-value pairs)。初始化和赋值操作可以通过不同的方式完成。

1.3.1 初始化 map

1.3.1.1 使用 make 初始化 map

使用 make 函数是最常见的初始化 map 的方式。make 会创建一个指定类型的空 map

m := make(map[string]int)

上面的代码会创建一个空的 map,键是 string 类型,值是 int 类型。

1.3.1.2 使用字面量初始化 map

你还可以使用字面量方式在声明时初始化 map,并为 map 指定初始值。

m := map[string]int{"a": 1,"b": 2,"c": 3,
}

这会创建一个包含初始键值对的 map,其中键是 string 类型,值是 int 类型。 

1.3 使用零值初始化 map

       如果你声明了一个 map,但没有进行初始化(即没有使用 make 或字面量),它会有一个零值,零值是 nil,表示该 map 尚未被初始化。此时,不能直接向 map 中添加元素,必须先进行初始化。

var m map[string]int
fmt.Println(m == nil)  // true

 此时如果尝试对 nilmap 进行赋值,会引发运行时错误。

m["a"] = 1  // 运行时错误:assignment to entry in nil map

1.3.2 赋值给 map

可以通过 map[key] = value 的方式来为 map 添加或修改元素。

1.3.2.1 添加元素
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
m["c"] = 3
fmt.Println(m)  // map[a:1 b:2 c:3]
1.3.2.2 修改元素

如果 map 中已经存在某个键,直接赋值会修改对应键的值。

m["b"] = 10
fmt.Println(m)  // map[a:1 b:10 c:3]
1.3.2.3 删除元素

使用 delete 函数可以从 map 中删除指定的键值对。

delete(m, "c")
fmt.Println(m)  // map[a:1 b:10]
1.3.2.4 判断 map 中的键是否存在

可以通过多重赋值来判断一个键是否存在于 map 中。

value, ok := m["a"]
if ok {fmt.Println("Key exists, value:", value)
} else {fmt.Println("Key does not exist")
}
  • value 是 map 中键对应的值。
  • ok 是一个布尔值,如果键存在,ok 为 true,否则为 false

1.4 map 的遍历

package mainimport "fmt"/*遍历map- key、value 无序的,遍历map,可能每次的结果排序都不一致。- "aaa" "aaaaa"- "bbb" "bbbbb"- "ccc" "ccccc"1、map是无序的,每次打印出来的map可能都不一样,它不能通过index获取,只能通过key来获取
2、map的长度是不固定的,是引用类型的
3、len可以用于map查看map中数据的数量,但是cap无法使用
4、map的key 可以是 布尔类型,整数,浮点数,字符串
*/
func main() {var map1 = map[string]int{"Go": 100, "Java": 99, "C": 80, "Python": 60}// 循环遍历,只能通过for range// 返回 key 和 valuefor k, v := range map1 {fmt.Println(k, v)}
}

1.5 map 结合 slice 使用

在遍历的时候,会发现 map 是无序的,所以结合 slice 使用。

package mainimport "fmt"// map 结合 slice 来使用/*
需求:
1、创建map来存储人的信息,name,age,sex,addr
2、每个map保存一个的信息
3、将这些map存入到切片中
4、打印这些数据
*/
func main() {user1 := make(map[string]string)user1["name"] = "kuangshen"user1["age"] = "27"user1["sex"] = "男"user1["addr"] = "重庆"user2 := make(map[string]string)user2["name"] = "feige"user2["age"] = "30"user2["sex"] = "男"user2["addr"] = "长沙"user3 := map[string]string{"name": "小蓝", "age": "18", "sex": "男", "addr": "火星"}fmt.Println(user3)// 3个数据有了,存放到切片中,供我们使用userDatas := make([]map[string]string, 0, 3)userDatas = append(userDatas, user1)userDatas = append(userDatas, user2)userDatas = append(userDatas, user3)fmt.Println(userDatas)// 0 map[string]stringfor _, user := range userDatas {//fmt.Println(i)fmt.Println(user["addr"])}}

1.6  map 不是线程安全的

       map 在 go 中不是线程安全的。也就是说,如果多个 goroutine 同时读写一个 map,就可能导致数据竞争、程序崩溃或者不一致的结果。

1.6.1 如何避免并发访问 map 时的安全问题

1. 使用 sync.Mutex 或 sync.RWMutex 来加锁

  sync.Mutex 可以确保在任意时刻只有一个 goroutine 可以访问 map,通过加锁和解锁的方式来保证互斥。

package mainimport ("fmt""sync"
)func main() {var m = make(map[string]int)var mu sync.Mutex // 创建一个互斥锁// 写操作:在对 map 进行写操作之前加锁mu.Lock()m["a"] = 1m["b"] = 2mu.Unlock() // 写完后解锁// 读操作:在对 map 进行读操作之前加锁mu.Lock()fmt.Println(m["a"])fmt.Println(m["b"])mu.Unlock()// 其他 goroutine 可以继续执行
}

       这里,mu.Lock()mu.Unlock() 保证了在 map 上进行读写时,同一时间只有一个 goroutine 可以操作 map

2. 使用 sync.RWMutex 来优化读写锁

  sync.RWMutex 允许多个 goroutine 同时进行读取操作,但在进行写操作时,锁会被独占,其他所有读写操作都会被阻塞。

package mainimport ("fmt""sync"
)func main() {var m = make(map[string]int)var mu sync.RWMutex // 创建一个读写锁// 写操作mu.Lock() // 加写锁m["a"] = 1m["b"] = 2mu.Unlock() // 解锁// 读操作mu.RLock() // 加读锁fmt.Println(m["a"])fmt.Println(m["b"])mu.RUnlock() // 解锁
}

       在这个例子中,mu.Lock() 用于写操作时,其他的读或写操作会被阻塞。而 mu.RLock() 允许多个 goroutine 同时读取 map

3. 使用 sync.Map

       Go 1.9 引入了 sync.Map,它是一个专门为并发设计的线程安全 map,在内部使用了高效的同步机制。相比普通的 mapsync.Map 在高并发场景下的性能会更好,因为它针对并发操作进行了优化。

package mainimport ("fmt""sync"
)func main() {var m sync.Map// 写操作m.Store("a", 1)m.Store("b", 2)// 读操作if value, ok := m.Load("a"); ok {fmt.Println("Value of a:", value)}// 删除元素m.Delete("b")// 遍历 mapm.Range(func(key, value interface{}) bool {fmt.Println(key, value)return true})
}

sync.Map 提供了几个重要方法:

  • Store(key, value):存储元素。
  • Load(key):加载元素,返回值和存在标志。
  • Delete(key):删除元素。
  • Range(func):遍历 mapRange 会按并发安全的方式逐一处理每个元素。

sync.Map 的主要优点是在读多写少的场景下,提供了更高的性能和更简便的并发安全保证。

二、list

2.1 list 和 slice 的区别

       在 Go 语言中,listslice 是常见的两种数据结构,但它们在实现和用法上有一些重要的区别。这里的“list”通常指的是链表结构(例如 container/list 包中的 List 类型),而 slice 是 Go 中的一种动态数组类型。让我们详细看看它们的区别:

2.1.1 定义和实现方式

Slice(切片)

  • slice 是一个动态数组,它是对底层数组的一个视图。切片的大小可以动态变化,但它始终指向底层的数组。
  • 切片由三部分组成:
    • 指针:指向底层数组的某个位置。
    • 长度:切片中元素的个数。
    • 容量:从切片的起始位置到底层数组的末尾的元素个数。
arr := []int{1, 2, 3}
slice := arr[1:3] // slice 包含 arr 中索引 1 到 2 的元素

List(链表)

  • list 是 Go 标准库中 container/list 包提供的双向链表实现。它与数组和切片不同,数据存储在由节点组成的链表中。每个节点包含数据和指向前一个和下一个节点的指针。
  • list 并没有像切片那样的固定容量限制,可以非常灵活地在链表的任意位置进行插入和删除。
import "container/list"l := list.New()
l.PushBack(1) // 将元素插入链表的尾部

2.1.2 存储方式 

  • Slice

    • 切片使用的是底层数组,因此它们的存储方式是连续的内存块。由于其基于数组,因此支持通过索引快速访问元素。
    • 切片的内存布局紧凑,性能较好,尤其是对于较大的数据集合,但可能存在内存分配的开销。
  • List

    • 链表是由节点组成的,每个节点包含数据和指向前后节点的指针。因此,它不需要连续的内存块来存储数据。
    • 链表的节点分散存储,内存分配和管理可能较为复杂,并且由于需要存储指针,内存开销较大。

2.1.3 性能差异

  • Slice

    • 切片对随机访问支持非常好,可以通过索引快速访问某个位置的元素,时间复杂度为 O(1)。
    • 切片的添加和删除操作可能会涉及到底层数组的扩容或缩小,尤其是当切片容量不足时,可能会发生重新分配内存,这可能导致性能下降。
    • 适合需要频繁读取和遍历的数据。
  • List

    • 链表的访问操作比切片慢,因为你需要从头节点或尾节点开始遍历链表。获取某个特定位置的元素的时间复杂度是 O(n)。
    • 在链表中插入和删除元素(尤其是在链表的中间)非常高效,时间复杂度为 O(1),因为你只需要改变节点的指针。
    • 适合频繁进行插入和删除操作的数据。

2.2 list 的基本用法

       在 Go 语言中,list 通常指的是 container/list 包中的双向链表。container/list 提供了一个双向链表的数据结构,可以进行高效的插入和删除操作。它不支持随机访问,但适合用于频繁修改的场景,比如需要频繁在头部、尾部或中间插入和删除元素时。

2.2.1 创建一个新的链表

可以使用 list.New() 创建一个新的空链表。

l := list.New()

2.2.2 插入元素

链表支持多种插入操作:

  • PushBack:向链表的尾部添加元素。
  • PushFront:向链表的头部添加元素。
  • InsertBefore:向指定节点之前插入元素。
  • InsertAfter:向指定节点之后插入元素。
// 向链表尾部添加元素
l.PushBack(1)
l.PushBack(2)// 向链表头部添加元素
l.PushFront(0)

2.2.3 遍历链表

       通过 Front()Back() 方法可以获取链表的头部和尾部元素。遍历链表时,使用 Next()Prev() 方法访问下一个或上一个节点。

// 从链表头部开始遍历
for e := l.Front(); e != nil; e = e.Next() {fmt.Println(e.Value)  // 输出节点的值
}

2.2.4 删除元素

链表提供了几种删除操作:

  • Remove:从链表中删除一个指定的元素。
  • MoveToFront:将一个元素移动到链表的头部。
  • MoveToBack:将一个元素移动到链表的尾部。
// 删除链表中的第一个元素
l.Remove(l.Front())// 删除链表中的最后一个元素
l.Remove(l.Back())

相关文章:

【go语言】map 和 list

一、map map 是一种无序的键值对的集合。 无序 :map[key]键值对:key - value map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。map 是一种集合,所以我们可以像迭代数组和切片那样迭代他。…...

PCIe 个人理解专栏——【2】LTSSM(Link Training and Status State Machine)

前言: 链路训练和状况状态机LTSSM(Link Training and Status State Machine)是整个链路训练和运行中状态的状态转换逻辑关系图,总共有11个状态。 正文: 包括检测(Detect),轮询&…...

《DiffIR:用于图像修复的高效扩散模型》学习笔记

paper:2303.09472 GitHub:GitHub - Zj-BinXia/DiffIR: This project is the official implementation of Diffir: Efficient diffusion model for image restoration, ICCV2023 目录 摘要 1、介绍 2、相关工作 2.1 图像恢复(Image Rest…...

Vue3 30天精进之旅:Day01 - 初识Vue.js的奇妙世界

引言 在前端开发领域,Vue.js是一款极具人气的JavaScript框架。它以其简单易用、灵活高效的特性,吸引了大量开发者。本文是“Vue 3之30天系列学习”的第一篇博客,旨在帮助大家快速了解Vue.js的基本概念和核心特性,为后续的深入学习…...

[笔记] 极狐GitLab实例 : 手动备份步骤总结

官方备份文档 : 备份和恢复极狐GitLab 一. 要求 为了能够进行备份和恢复,请确保您系统已安装 Rsync。 如果您安装了极狐GitLab: 如果您使用 Omnibus 软件包,则无需额外操作。如果您使用源代码安装,您需要确定是否安装了 rsync。…...

将本地项目上传到 GitLab/GitHub

以下是将本地项目上传到 GitLab 的完整步骤,从创建仓库到推送代码的详细流程: 1. 在 GitLab 上创建新项目 登录 GitLab,点击 New project。选择 Create blank project。填写项目信息: Project name: 项目名称(如 my-p…...

switch组件的功能与用法

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了PageView这个Widget,本章回中将介绍Switch Widget.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的Switch是指左右滑动的开关,常用来表示某项设置是打开还是关闭。Fl…...

mac 电脑上安装adb命令

在Mac下配置android adb命令环境,配置方式如下: 1、下载并安装IDE (android studio) Android Studio官网下载链接 详细的安装连接请参考 Mac 安装Android studio 2、配置环境 在安装完成之后,将android的adb工具所在…...

Couchbase UI: Dashboard

以下是 Couchbase UI Dashboard 页面详细介绍,包括页面布局和功能说明,帮助你更好地理解和使用。 1. 首页(Overview) 功能:提供集群的整体健康状态和性能摘要 集群状态 节点健康状况:绿色(正…...

[极客大挑战 2019]Knife1

题目 蚁剑直接连接密码是Syc 拿下flag flag{1d373584-fc74-4a2c-a6d4-3691314be4ab}...

第17篇:python进阶:详解数据分析与处理

第17篇:数据分析与处理 内容简介 本篇文章将深入探讨数据分析与处理在Python中的应用。您将学习如何使用pandas库进行数据清洗与分析,掌握matplotlib和seaborn库进行数据可视化,以及处理大型数据集的技巧。通过丰富的代码示例和实战案例&am…...

【Maui】提示消息的扩展

文章目录 前言一、问题描述二、解决方案三、软件开发(源码)3.1 消息扩展库3.2 消息提示框使用3.3 错误消息提示使用3.4 问题选择框使用 四、项目展示 前言 .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架,用于使用 C# 和 XAML 创建本机移…...

消息队列篇--通信协议扩展篇--二进制编码(ASCII,UTF-8,UTF-16,Unicode等)

1、ASCII(American Standard Code for Information Interchange) 范围:0 到 127(共 128 个字符)描述:ASCII 是一种早期的字符编码标准,主要用于表示英文字母、数字和一些常见的符号。每个字符占…...

CentOS 7 搭建lsyncd实现文件实时同步 —— 筑梦之路

在 CentOS 7 上搭建 lsyncd(Live Syncing Daemon)以实现文件的实时同步,可以按照以下步骤进行操作。lsyncd 是一个基于 inotify 的轻量级实时同步工具,支持本地和远程同步。以下是详细的安装和配置步骤: 1. 系统准备 …...

【2025最新计算机毕业设计】基于SSM房屋租赁平台【提供源码+答辩PPT+文档+项目部署】(高质量源码,可定制,提供文档,免费部署到本地)

作者简介:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容:🌟Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…...

(开源)基于Django+Yolov8+Tensorflow的智能鸟类识别平台

1 项目简介(开源地址在文章结尾) 系统旨在为了帮助鸟类爱好者、学者、动物保护协会等群体更好的了解和保护鸟类动物。用户群体可以通过平台采集野外鸟类的保护动物照片和视频,甄别分类、实况分析鸟类保护动物,与全世界各地的用户&…...

【转帖】eclipse-24-09版本后,怎么还原原来版本的搜索功能

【1】原贴地址:eclipse - 怎么还原原来版本的搜索功能_eclipse打开类型搜索类功能失效-CSDN博客 https://blog.csdn.net/sinat_32238399/article/details/145113105 【2】原文如下: 更新eclipse-24-09版本后之后,新的搜索功能(CT…...

【自定义函数】编码-查询-匹配

目录 自定义编码匹配编码匹配改进 sheet来源汇总来源汇总改进 END 自定义编码匹配 在wps vb环境写一个新的excel函数名为编码匹配,第一个参数指定待匹配文本所在单元格(相对引用),第二个参数指定关键词区域(绝对引用&…...

16 分布式session和无状态的会话

在我们传统的应用中session存储在服务端,减少服务端的查询压力。如果以集群的方式部署,用户登录的session存储在该次登录的服务器节点上,如果下次访问服务端的请求落到其他节点上就需要重新生成session,这样用户需要频繁的登录。 …...

git基础指令大全

版本控制 git管理文件夹 进入要管理的文件夹 — 进入 初始化(提名) git init 管理文件夹 生成版本 .git ---- git在管理文件夹时,版本控制的信息 生成版本 git status 检测当前文件夹下的文件状态 (检测,检测之后就要管理了…...

Android实训九 数据存储和访问

实训9 数据存储和访问 一、【实训目的】 1、 SharedPreferences存储数据; 2、 借助Java的I/O体系实现文件的存储, 3、使用Android内置的轻量级数据库SQLite存储数据; 二、【实训内容】 1、实现下图所示的界面,实现以下功能: 1&#xff…...

[STM32 标准库]定时器输出PWM配置流程 PWM模式解析

前言: 本文内容基本来自江协,整理起来方便日后开发使用。MCU:STM32F103C8T6。 一、配置流程 1、开启GPIO,TIM的时钟 /*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockC…...

如何跨互联网adb连接到远程手机-蓝牙电话集中维护

如何跨互联网adb连接到远程手机-蓝牙电话集中维护 --ADB连接专题 一、前言 随便找一个手机,安装一个App并简单设置一下,就可以跨互联网的ADB连接到这个手机,从而远程操控这个手机做各种操作。你敢相信吗?而这正是本篇想要描述的…...

【6】YOLOv8 训练自己的分割数据集

YOLOv8 训练自己的分割数据集:详细指南 引言 YOLOv8作为目标检测领域的佼佼者,其在实例分割任务上也表现出色。本文将详细介绍如何使用YOLOv8训练自己的分割数据集,从数据集准备、模型训练、评估到部署,全方位地进行阐述。 一、数据集准备 数据收集: 图像/视频来源: 与目…...

将 OneLake 数据索引到 Elasticsearch - 第二部分

作者:来自 Elastic Gustavo Llermaly 及 Jeffrey Rengifo 本文分为两部分,第二部分介绍如何使用自定义连接器将 OneLake 数据索引并搜索到 Elastic 中。 在本文中,我们将利用第 1 部分中学到的知识来创建 OneLake 自定义 Elasticsearch 连接器…...

Android Studio:视图绑定的岁月变迁(2/100)

一、博文导读 本文是基于Android Studio真实项目,通过解析源码了解真实应用场景,写文的视角和读者是同步的,想到看到写到,没有上帝视角。 前期回顾,本文是第二期。 private Unbinder mUnbinder; 只是声明了一个 接口…...

私有包上传maven私有仓库nexus-2.9.2

一、上传 二、获取相应文件 三、最后修改自己的pom文件...

Qt监控系统辅屏预览/可以同时打开4个屏幕预览/支持5x64通道预览/onvif和rtsp接入/性能好

一、前言说明 在监控系统中,一般主界面肯定带了多个通道比如16/64通道的画面预览,随着电脑性能的增强和多屏幕的发展,再加上现在监控摄像头数量的增加,越来越多的用户希望在不同的屏幕预览不同的实时画面,一个办法是打…...

Vue.js 路由懒加载

Vue.js 路由懒加载 在 Vue.js 开发中,随着应用规模的扩大,打包后的 JavaScript 文件可能会变得相当庞大,影响页面的加载速度和性能。为了解决这个问题,Vue Router 提供了路由懒加载功能,可以将不同路由对应的组件分割…...

HBase的原理

一、什么是HBase HBase是一个分布式,版本化,面向列的数据库,依赖Hadoop和Zookeeper (1)HBase的优点 提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统 (2) HBase 表的特性 Region包含多行 列族包含多…...