0

0

Golangmap定义与常用操作实例

P粉602998670

P粉602998670

发布时间:2025-09-14 13:32:01

|

453人浏览过

|

来源于php中文网

原创

nil map是未初始化的map,不能写入但读取安全;空map用make初始化,可读写。需写入时应使用空map,仅判断存在性可用nil map。

golangmap定义与常用操作实例

Golang中的

map
是一种非常灵活且强大的数据结构,它本质上是一个无序的键值对集合,通过哈希表实现,允许我们以极快的速度进行数据查找、插入和删除。理解它的定义和熟练掌握常用操作,是Go语言开发中不可或缺的基础。

Golang的

map
,说白了,就是一种键值对的集合,我们用一个唯一的键(key)去关联一个值(value)。它在Go语言里是引用类型,这意味着当你把一个
map
赋值给另一个变量时,它们指向的是同一个底层数据结构。这和数组、切片(slice)在很多方面都不同,尤其是在内存布局和访问模式上。

Golang Map的定义与基本操作实例

我们来具体看看

map
是怎么定义和使用的。

1. 定义与初始化

立即学习go语言免费学习笔记(深入)”;

创建一个

map
最常见的方式是使用
make
函数。你可以指定键和值的类型:

// 定义一个键为string,值为int的map
var m1 map[string]int

// 使用make初始化,此时m1不再是nil,但容量为0
m1 = make(map[string]int)

// 也可以在声明时直接初始化
m2 := make(map[string]string)

// 或者,如果你知道初始元素,可以直接使用字面量方式
m3 := map[string]bool{
    "active": true,
    "admin":  false,
}

// 还可以指定初始容量,这有助于减少后续的内存重新分配,提高性能
// 但Go运行时会根据实际需要动态调整容量,所以这只是一个建议值
m4 := make(map[int]string, 10)

需要注意的是,一个未经

make
初始化的
map
nil
。对
nil
map
进行写操作会导致运行时panic,但读操作是安全的,会返回值的零值。

2. 赋值与更新

map
添加新元素或更新现有元素非常直观:

m1["apple"] = 1
m1["banana"] = 2
fmt.Println("m1:", m1) // 输出: m1: map[apple:1 banana:2]

m1["apple"] = 3 // 更新"apple"的值
fmt.Println("m1 (updated):", m1) // 输出: m1 (updated): map[apple:3 banana:2]

3. 获取值

map
中获取值也很简单,但通常我们会使用“comma ok”模式来判断键是否存在:

value, ok := m1["apple"]
if ok {
    fmt.Printf("Key 'apple' exists, value is %d\n", value) // 输出: Key 'apple' exists, value is 3
} else {
    fmt.Println("Key 'apple' does not exist")
}

// 如果键不存在,直接访问会得到值的零值
valueOnly := m1["orange"]
fmt.Printf("Value for 'orange' (if not exists): %d\n", valueOnly) // 输出: Value for 'orange' (if not exists): 0

ok
这个布尔值非常关键,因为它能区分一个键不存在和键存在但其值为零值这两种情况。

4. 删除元素

使用内置的

delete
函数可以从
map
中移除一个键值对:

delete(m1, "banana")
fmt.Println("m1 (after delete):", m1) // 输出: m1 (after delete): map[apple:3]

// 删除不存在的键不会报错,也不会有任何操作
delete(m1, "grape")
fmt.Println("m1 (delete non-existent):", m1) // 输出: m1 (delete non-existent): map[apple:3]

5. 遍历

map

map
的遍历通常使用
for...range
循环。需要记住的是,
map
是无序的,所以每次遍历的顺序可能不同:

for key, value := range m1 {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
}

// 如果你只关心键或只关心值,可以省略另一个
for key := range m1 {
    fmt.Printf("Only Key: %s\n", key)
}

6. 获取

map
长度

使用

len
函数可以获取
map
中键值对的数量:

fmt.Printf("Length of m1: %d\n", len(m1)) // 输出: Length of m1: 1

这些就是

map
最基础也最常用的操作。掌握它们,你就能在Go中有效地组织和管理数据了。

Golang Map的底层实现机制是怎样的?它与Slice有何区别

深入理解

map
的底层,才能更好地驾驭它,避免一些潜在的性能坑。Go语言的
map
底层实现是一个哈希表(hash table),具体来说,它是一个经过优化的B树(B-tree)或红黑树(Red-Black Tree)的变种,但更准确地说是基于哈希桶(hash bucket)的实现。

当你创建一个

map
时,Go运行时会分配一个
hmap
结构体,其中包含了指向一系列哈希桶(
bmap
)的指针。每个哈希桶可以存储多个键值对。当一个键被插入时,Go会计算它的哈希值,然后根据哈希值找到对应的哈希桶。如果多个键的哈希值映射到同一个桶(哈希冲突),这些键值对会以链表的形式存储在同一个桶中,或者溢出到额外的溢出桶(overflow bucket)中。Go的
map
在负载因子(load factor,即平均每个桶存储的元素数量)达到一定阈值时,会自动进行扩容(rehashing),重新分配更大的内存空间,并重新组织所有键值对,以保持查找效率。

它与Slice有何区别?

map
slice
在Go中都是非常重要的数据结构,但它们的设计哲学和使用场景截然不同:

  1. 有序性

    • slice
      :是有序的,元素通过索引(0到
      len-1
      )访问,顺序是固定的。
    • map
      :是无序的,元素通过键访问,遍历顺序不确定,每次可能都不同。这反映了其哈希表的本质。
  2. 访问方式

    • slice
      :通过整数索引访问元素,例如
      s[0]
    • map
      :通过键(可以是任意可比较类型,如
      string
      ,
      int
      ,
      struct
      等)访问元素,例如
      m["key"]
  3. 内存布局

    • slice
      :在内存中是连续存储的,这使得基于索引的访问非常高效。
    • map
      :元素在内存中是分散存储的,通过哈希函数和指针链接,查找效率依赖于哈希函数的质量和冲突解决机制。
  4. 零值

    成新网络商城购物系统
    成新网络商城购物系统

    使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888

    下载
    • slice
      :零值是
      nil
      len
      cap
      都是0。
    • map
      :零值也是
      nil
      len
      是0。对
      nil
      map
      进行读操作是安全的,但写操作会导致panic。
  5. 扩容机制

    • slice
      :当容量不足时,会创建一个新的更大底层数组,并将旧元素拷贝过去。
    • map
      :当负载因子过高时,会进行扩容,重新计算所有键的哈希值并分布到新的桶中。
  6. 用途

    • slice
      :适用于需要顺序访问、列表、栈、队列等场景。
    • map
      :适用于需要快速查找、存储配置、计数、关联数据等场景。

我个人在使用时,经常会根据数据是否需要“顺序”来决定使用

slice
还是
map
。如果我需要一个列表,或者需要按照插入顺序处理数据,
slice
是首选。但如果我需要一个快速的查找表,或者需要根据某个标识符来获取数据,那
map
的优势就无可替代了。

在并发场景下,如何安全地操作Golang Map?有哪些常见的陷阱和解决方案?

map
在Go语言中不是并发安全的,这是它一个非常重要的特性。如果在多个goroutine中同时对同一个
map
进行读写操作,就会引发数据竞争(data race),导致程序崩溃(panic)或者产生不可预期的结果。这对我来说,是Go新手最容易踩的坑之一。

常见的陷阱:

  1. 直接并发读写:最直接的错误就是不加任何保护地在多个goroutine中同时修改或读取
    map
    // 示例:会引发panic
    // var m = make(map[string]int)
    // go func() {
    //     for i := 0; i < 1000; i++ {
    //         m[fmt.Sprintf("key%d", i)] = i
    //     }
    // }()
    // go func() {
    //     for i := 0; i < 1000; i++ {
    //         _ = m[fmt.Sprintf("key%d", i)]
    //     }
    // }()
    // time.Sleep(time.Second) // 等待goroutine执行

    Go运行时会检测到这种并发不安全的操作,并通常会报告一个

    fatal error: concurrent map writes
    concurrent map reads and writes

解决方案:

针对并发操作

map
,Go提供了几种成熟的方案:

  1. 使用

    sync.Mutex
    进行锁定

    这是最直接、最通用的方法。通过互斥锁(

    Mutex
    )来确保在任何给定时间只有一个goroutine可以访问
    map

    import (
        "fmt"
        "sync"
        "time"
    )
    
    type SafeMap struct {
        mu    sync.Mutex
        data map[string]int
    }
    
    func NewSafeMap() *SafeMap {
        return &SafeMap{
            data: make(map[string]int),
        }
    }
    
    func (sm *SafeMap) Set(key string, value int) {
        sm.mu.Lock()
        defer sm.mu.Unlock()
        sm.data[key] = value
    }
    
    func (sm *SafeMap) Get(key string) (int, bool) {
        sm.mu.Lock()
        defer sm.mu.Unlock()
        val, ok := sm.data[key]
        return val, ok
    }
    
    // func main() {
    //     safeM := NewSafeMap()
    //     var wg sync.WaitGroup
    //
    //     for i := 0; i < 100; i++ {
    //         wg.Add(1)
    //         go func(i int) {
    //             defer wg.Done()
    //             safeM.Set(fmt.Sprintf("key%d", i), i)
    //         }(i)
    //     }
    //
    //     for i := 0; i < 50; i++ {
    //         wg.Add(1)
    //         go func(i int) {
    //             defer wg.Done()
    //             val, ok := safeM.Get(fmt.Sprintf("key%d", i))
    //             if ok {
    //                 // fmt.Printf("Read key%d: %d\n", i, val)
    //             }
    //         }(i)
    //     }
    //
    //     wg.Wait()
    //     fmt.Println("SafeMap operations completed.")
    // }

    优点:简单易懂,适用于所有并发场景。 缺点:读操作也会阻塞写操作,写操作也会阻塞读操作,如果读多写少,性能可能会受影响。

  2. 使用

    sync.RWMutex
    进行读写锁

    RWMutex
    Mutex
    的升级版,它允许多个goroutine同时进行读操作,但写操作仍然是排他的。当有写操作时,所有读写操作都会被阻塞。这在读多写少的场景下能显著提升性能。

    type RWSafeMap struct {
        mu    sync.RWMutex
        data map[string]int
    }
    
    func NewRWSafeMap() *RWSafeMap {
        return &RWSafeMap{
            data: make(map[string]int),
        }
    }
    
    func (rsm *RWSafeMap) Set(key string, value int) {
        rsm.mu.Lock() // 写锁
        defer rsm.mu.Unlock()
        rsm.data[key] = value
    }
    
    func (rsm *RWSafeMap) Get(key string) (int, bool) {
        rsm.mu.RLock() // 读锁
        defer rsm.mu.RUnlock()
        val, ok := rsm.data[key]
        return val, ok
    }
    // 使用方式与SafeMap类似,只是内部锁类型不同

    优点:在读多写少的场景下,性能优于

    sync.Mutex
    缺点:相比
    sync.Mutex
    略复杂,需要区分读锁和写锁。

  3. 使用

    sync.Map
    (Go 1.9+)

    sync.Map
    是Go 1.9版本引入的并发安全
    map
    ,它专门为并发场景设计,在某些特定场景下(例如,键是稳定增长的,且不经常删除),性能优于
    RWMutex
    。它的内部实现非常巧妙,包含一个只读部分(
    read
    )和一个可写部分(
    dirty
    ),通过“copy-on-write”策略来优化并发读写。

    import "sync"
    
    // func main() {
    //     var m sync.Map
    //     var wg sync.WaitGroup
    //
    //     for i := 0; i < 100; i++ {
    //         wg.Add(1)
    //         go func(i int) {
    //             defer wg.Done()
    //             m.Store(fmt.Sprintf("key%d", i), i) // 存储键值对
    //         }(i)
    //     }
    //
    //     for i := 0; i < 50; i++ {
    //         wg.Add(1)
    //         go func(i int) {
    //             defer wg.Done()
    //             if val, ok := m.Load(fmt.Sprintf("key%d", i)); ok { // 获取值
    //                 // fmt.Printf("Read sync.Map key%d: %d\n", i, val)
    //             }
    //         }(i)
    //     }
    //
    //     wg.Wait()
    //     fmt.Println("sync.Map operations completed.")
    //
    //     // 遍历sync.Map
    //     m.Range(func(key, value interface{}) bool {
    //         // fmt.Printf("Key: %v, Value: %v\n", key, value)
    //         return true // 返回true继续遍历,返回false停止遍历
    //     })
    // }

    sync.Map
    的API

    • Store(key, value interface{})
      :存储键值对。
    • Load(key interface{}) (value interface{}, ok bool)
      :获取值。
    • LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
      :如果键存在则加载,否则存储。
    • Delete(key interface{})
      :删除键。
    • Range(f func(key, value interface{}) bool)
      :遍历
      map

    优点:在特定场景下(高并发读,少量写,键不经常删除)性能极佳,通常优于

    RWMutex
    。API设计更简洁。 缺点:不适用于所有场景,例如,如果键的更新或删除非常频繁,
    sync.Map
    的性能可能不如
    RWMutex
    。并且,键和值必须是
    interface{}
    类型,这意味着需要类型断言,可能带来一些运行时开销和不便。

选择哪种方案取决于你的具体需求和性能瓶颈。对于我来说,如果只是简单的并发保护,

sync.Mutex
sync.RWMutex
足够了。但如果遇到高并发、读多写少的场景,且对性能有极致要求,
sync.Map
无疑是更好的选择,尽管它要求我们处理
interface{}
类型转换。

Golang Map的零值(nil map)与空Map有什么不同?何时应该使用它们?

这又是一个Go语言里常常让人感到困惑,但一旦理解就豁然开朗的细节。

nil map
和“空
map
”虽然表面上看起来都是空的,但它们的行为和底层状态有着本质的区别。

1.

nil map

当一个

map
变量被声明但没有经过
make
函数初始化时,它的值就是
nil

var nilMap map[string]int
fmt.Println("nilMap:", nilMap)         // 输出: nilMap: map[]
fmt.Println("Is nilMap nil?", nilMap == nil) // 输出: Is nilMap nil? true
fmt.Println("Length of nilMap:", len(nilMap)) // 输出: Length of nilMap: 0

nil map
的特性:

  • 不能写入:尝试向
    nil map
    中添加元素会引发运行时panic。
    // nilMap["key"] = 1 // 这行代码会引发 panic: assignment to entry in nil map
  • 可以读取:从
    nil map
    中读取元素是安全的,会返回该值类型的零值。
    val, ok := nilMap["nonexistent"]
    fmt.Printf("Read from nilMap: val=%d, ok=%t\n", val, ok) // 输出: Read from nilMap: val=0, ok=false
  • 长度为0
    len(nilMap)
    返回0。
  • 可以迭代
    for...range
    循环
    nil map
    不会有任何操作,也不会panic。

2. 空Map

一个空

map
是通过
make
函数初始化但尚未添加任何元素的
map

emptyMap := make(map[string]int)
fmt.Println("emptyMap:", emptyMap)         // 输出: emptyMap: map[]
fmt.Println("Is emptyMap nil?", emptyMap == nil) // 输出: Is emptyMap nil? false
fmt.Println("Length of emptyMap:", len(emptyMap)) // 输出: Length of emptyMap: 0

map
的特性:

  • 可以写入:可以正常添加、修改元素。
    emptyMap["key"] = 1
    fmt.Println("emptyMap after write:", emptyMap) // 输出: emptyMap after write: map[key:1]
  • 可以读取:和普通
    map
    一样,可以安全读取。
  • 长度为0(初始)
    len(emptyMap)
    返回0,但随着元素添加会改变。
  • 可以迭代
    for...range
    循环空
    map
    不会有任何操作。

何时应该使用它们?

理解这两者的区别

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

182

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

229

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

343

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

209

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

394

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

220

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

193

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

418

2025.06.17

clawdbot ai使用教程 保姆级clawdbot部署安装手册
clawdbot ai使用教程 保姆级clawdbot部署安装手册

Clawdbot是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

14

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 4.3万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号