GO map(基础用法+底层逻辑+并发安全)

一、Map 是什么

Go 中 map 是 ** 基于哈希表实现的键值对(key-value)** 集合。

  • 键唯一,元素无序
  • 引用类型,必须初始化才能使用
  • 增删改查时间复杂度:O(1)
  • 开发高频用于:字典、缓存、去重、映射关系
基础语法格式
map[KeyType]ValueType

二、Map 四种初始化方式

1. 先声明、再初始化

var m map[string]int // 仅声明,此时为 nil
m = make(map[string]int)

2. 直接 make 初始化(最常用)

// 不指定容量
m := make(map[string]int)

// 指定初始容量(推荐,减少扩容开销)
m := make(map[string]int, 8)

3. 字面量直接初始化 + 赋值

m := map[string]int{
	"张三": 18,
	"李四": 20,
}

4. var 声明 + 字面量

var m = map[int]string{
	1: "Go",
	2: "Java",
}

核心铁律:未 make 的 nil map 不能直接存键值,会直接 panic

三、Map 基础操作:增 / 删 / 改 / 查

1. 新增 & 修改

语法统一:

  • key 不存在 → 新增
  • key 已存在 → 覆盖修改
    m := make(map[string]int)
    m["数学"] = 90  // 新增
    m["数学"] = 99  // 修改覆盖

2. 查找取值

双返回值(应用场景广

val, ok := m["英语"]
if ok {
	fmt.Println("键存在,值:", val)
} else {
	fmt.Println("键不存在")
}

单反回值(无法区分‘0’和不存在)

val := m["英语"]

3. 删除元素

使用内置 delete 函数

// 格式:delete(map, key)
delete(m, "数学")

即使 key 不存在,delete 也不会报错

4. 获取元素个数

cnt := len(m)

四、Map 遍历 for range

map 遍历完全无序,每次遍历顺序可能不同。

1. 遍历 key + value

for k, v := range m {
	fmt.Println(k, v)
}

2. 只遍历 key

for k := range m {
	fmt.Println(k)
}

3. 只遍历 value

for _, v := range m {
	fmt.Println(v)
}

五、Key 类型限制

合法 key(可比较类型)

int、string、bool、数组、普通结构体(无引用成员)

非法 key(不可比较)

slice、map、function、包含切片或 Map 的结构体原因:哈希表要求 key 必须可比较、能计算哈希值

六、Map 引用类型特性

map 是引用类型

  1. 赋值、函数传参,都是传递引用
  2. 函数内修改 map 数据,会直接影响原 map

示例

func modify(m map[int]int) {
	m[1] = 999
}

func main() {
	m := make(map[int]int)
	modify(m)
}

七、Map 底层逻辑

1.底层基于哈希表 + 数组 + 链表或红黑树实现
2..核心结构

hmap:map 顶层结构体

bmap:桶 bucket,每个桶存放多个 key-value

3.哈希冲突

不同 key 哈希取模后落到同一个桶

早期链表解决冲突

新版 Go 桶内元素过多转为红黑树,提升查询效率

4.扩容机制

负载因子达到阈值,触发翻倍扩容渐进式扩容不会一次性迁移所有数据,减少卡顿

5.为什么 map 无序

扩容、桶迁移、哈希随机种子,都会导致遍历顺序打乱

八、Map 并发安全

1. 原生 map 并发不安全

  • 只读不写 并发安全
  • 并发写 或 读写混合 直接触发 fatal error: concurrent map writes

2. 并发解决方案

方案 1:加读写锁(RWMutex)

适合读多写少场景

import "sync"

var (
	mu sync.RWMutex
	m  = make(map[string]int)
)

// 写操作 加写锁
mu.Lock()
m["a"] = 1
mu.Unlock()

// 读操作 加读锁
mu.RLock()
val, ok := m["a"]
mu.RUnlock()
方案 2:使用官方并发安全 Map

sync.Map

  • 适合高并发、读写差异大的场景
  • 无需手动加锁,开箱即用

九、完整可运行 Demo

package main

import "fmt"

func main() {
	// 初始化
	score := make(map[string]int, 4)

	// 增改
	score["语文"] = 95
	score["数学"] = 100

	// 查询
	val, ok := score["英语"]
	if !ok {
		fmt.Println("英语成绩不存在")
	}

	// 删除
	delete(score, "语文")

	// 遍历
	for sub, s := range score {
		fmt.Printf("%s:%d\n", sub, s)
	}
}

十、总结

  • map 键唯一、遍历无序、引用类型
  • 必须 make 初始化,nil map 禁止写入
  • 取值务必用 val, ok 双返回值
  • 切片、map、函数不能作为 key
  • 原生 map 并发写不安全,需配合锁或 sync.Map
  • 底层哈希桶结构,存在哈希冲突与渐进式扩容
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值