【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())

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

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

相关文章

Python自动化运维:一键掌控服务器的高效之道

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在互联网和云计算高速发展的今天,服务器数量的指数增长使得手动运维和管理变得异常繁琐。Python凭借其强大的可读性和丰富的生态系统,成为…

数据融合的经典模型:早期融合、中期融合与后期融合的对比

在多模态数据融合中,如何将不同模态(如图像、文本、语音等)的数据整合到一个统一的表示中,是至关重要的环节。不同的任务需求和数据特点决定了我们应该采用哪种融合策略,而早期融合、中期融合和后期融合是多模态数据处…

使用python调用JIRA6 进行OAuth1认证获取AccessToken

Jira配置应用程序链接 1) 创建应用程序链接 登录 JIRA 管理后台。转到 Administration > Applications > Application Links。在输入框中输入外部应用程序的 URL(例如 GitLab 或自定义应用),然后点击 Create new link。 2) 配置 Con…

WPF基础 | WPF 基础概念全解析:布局、控件与事件

WPF基础 | WPF 基础概念全解析:布局、控件与事件 一、前言二、WPF 布局系统2.1 布局的重要性与基本原理2.2 常见布局面板2.3 布局的测量与排列过程 三、WPF 控件3.1 控件概述与分类3.2 常见控件的属性、方法与事件3.3 自定义控件 四、WPF 事件4.1 路由事件概述4.2 事…

2K高刷电竞显示器推荐

2K高刷电竞显示器推荐,各位喜欢打游戏,身为电竞迷的小伙伴,如果你想选一款2K高刷电竞显示器,那么下面的内容不容错过。 1.HKC G27H4Pro - 2K高刷电竞显示器推荐 外观 - HKC G27H4Pro 2K高刷电竞显示器 初见 HKC G27H4Pro&#x…

【29】Word:李楠-学术期刊❗

目录 题目​ NO1.2.3.4.5 NO6.7.8 NO9.10.11 NO12.13.14.15 NO16 题目 NO1.2.3.4.5 另存为手动/F12Fn光标来到开头位置处→插入→封面→选择花丝→根据样例图片,对应位置填入对应文字 (手动调整即可)复制样式:开始→样式对话框→管理…

破解浏览器渲染“死锁”:CSS与JS如何影响页面加载速度?

破解浏览器渲染“死锁”:CSS与JS如何影响页面加载速度? 在这个快速发展的Web世界里,性能是开发者们永恒的追求。当你打开一个网页,可能会注意到一些页面加载特别慢,甚至产生短暂的“白屏”,你有没有想过&a…

1.CSS的三大特性

css有三个非常重要的三个特性&#xff1a;层叠性、继承性、优先级 1.1 层叠性 想通选择器给设置想听的样式&#xff0c;此时一个样式就会覆盖&#xff08;层叠&#xff09;另一个冲突的样式。层叠性主要是解决样式冲突的问题。 <!DOCTYPE html> <html lang"en&…

随机矩阵投影长度保持引理及其证明

原论文中的引理 2 \textbf{2} 2 1. \textbf{1. } 1. 引理 1 \textbf{1} 1(前提之一) 1.1. \textbf{1.1. } 1.1. 引理 1 \textbf{1} 1的内容 &#x1f449;前提&#xff1a; X ∼ N ( 0 , σ ) X\sim{}N(0,\sigma) X∼N(0,σ)即 f ( x ) 1 2 π σ e – x 2 2 σ 2 f(x)\text{}…

C语言-构造数据类型

1、构造数据类型 结构体、共用体、枚举。 2、结构体 1、结构体的定义 结构体是一个自定义的复合数据类型&#xff0c;它允许将不同类型的数据组合在一起。 struct 结构体名 {数据类型1 成员变量1;数据类型2 成员变量2;数据类型3 成员变量3;数据类型4 成员变量4; } 2、结构体变…

Effective C++ 规则48: 认识 Template 元编程

1、什么是模板元编程 模板元编程是指利用 C 的模板机制&#xff0c;在编译期执行某些逻辑运算或代码生成的技术。通过模板元编程&#xff0c;可以在编译阶段完成类型推导、条件分支、递归计算等任务。C 语言的模板功能不仅仅是为了解决类型参数化的问题&#xff0c;它还可以在…

CSS:跑马灯

<div class"swiper-container"><div class"swiper-wrapper"><!-- 第一组 --><div class"item" v-for"item in cardList" :key"first-item.id"><img :src"item.image" alt""…

99.16 金融难点通俗解释:营业总收入

目录 0. 承前1. 简述2. 比喻&#xff1a;小明家的小卖部2.1 第一步&#xff1a;了解小卖部的收入来源2.2 第二步&#xff1a;计算一天的收入2.3 第三步&#xff1a;理解营业总收入 3. 生活中的例子3.1 小卖部的一周营业3.2 不同季节的变化 4. 小朋友要注意4.1 营业总收入不等于…

MyBatis Plus 的 InnerInterceptor:更轻量级的 SQL 拦截器

在 Spring Boot 项目中使用 MyBatis Plus 时&#xff0c;你可能会遇到 InnerInterceptor 这个概念。 InnerInterceptor 是 MyBatis Plus 提供的一种轻量级 SQL 拦截器&#xff0c;它与传统的 MyBatis 拦截器&#xff08;Interceptor&#xff09;有所不同&#xff0c;具有更简单…

CLOUDFLARE代理请求重定向你太多次

现象 使用CLOUDFLARE代理前请求正常&#xff0c;使用CLOUDFLARE代理请求后出现 原因分析 以下是我的猜测&#xff0c;在默认情况下 CLOUDFLARE代理&#xff0c;可能是直接请求我们服务器的IP&#xff0c;比如&#xff1a;http://1.1.1.1 而不是通过域名的方式&#xff08;如…

大模型开发 | RAG在实际开发中可能遇到的坑

近年来&#xff0c;大语言模型 (LLM) 的飞速发展令人瞩目&#xff0c;它们在各个领域展现出强大的应用潜力。然而&#xff0c;LLM 也存在一些固有的局限性&#xff0c;例如知识更新滞后、信息编造 (幻觉) 等问题。为了克服这些挑战&#xff0c;检索增强生成 (Retrieval-Augment…

DDD架构实战第五讲总结:将领域模型转化为代码

云架构师系列课程之DDD架构实战第五讲总结:将领域模型转化为代码 一、引言 在前几讲中,我们讨论了领域模型的重要性及其在业务分析中的渐进获得方法。本讲将聚焦于如何将领域模型转化为代码,使得开发人员能够更轻松地实现用户的领域模型。 二、从模型到代码:领域驱动设计…

AI Agent的多轮对话:提升用户体验的关键技巧

在前面的文章中&#xff0c;我们讨论了 AI Agent 的各个核心系统。今天&#xff0c;我想聊聊如何实现一个好用的多轮对话系统。说实话&#xff0c;这个话题我琢磨了很久&#xff0c;因为它直接影响到用户体验。 从一个槽点说起 还记得我最开始做对话系统时的一个典型场景&…

vue router路由复用及刷新问题研究

路由复用问题 当路由匹配路径未发生变化时&#xff0c;只是相关的参数发生了变化&#xff0c;路由跳转时&#xff0c;会发现虽然地址栏中的地址更新到了新的链接&#xff0c;但是页面渲染并未触发响应路由组件的created,mounted等钩子函数&#xff0c;也就意味着组件并没有被重…

Android各个版本存储权限适配

一、Android6.0-9.0 1、动态权限申请&#xff1a; private static String[] arrPermissions {android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE,android.Manifest.permission.ACCESS_FINE_LOCATION,android.Manifest.…