go20/day02/map/README.md
yumaojun03 54415d45ba ```
feat(day02): 添加Go语言基础数据类型教程

新增map和slice详细教程,完善array和struct内容

- 新增map章节,详细介绍Go中map的概念、创建、操作和底层原理
- 新增slice章节,涵盖切片的创建、操作、引用特性和实际应用
- 完善array章节,添加数组作为函数参数、多维数组等内容
- 更新struct章节,修正标题错误并补充结构体定义说明
- 为各章节添加实践作业题目,增强学习效果
```
2026-01-11 13:56:26 +08:00

236 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Map
Go中的map是一种无序的键值对集合底层基于哈希表实现。每个键必须是可比较的类型值可以是任意类型。map提供快速的查找、插入和删除操作。
## 核心概念
map的底层实现是哈希表通过键的哈希值快速定位值。键必须是可哈希的类型如int、string、指针等值可以是任意类型。map是引用类型赋值时共享底层数据。
```go
// map的零值是nil不能直接使用
var m map[string]int // m == nil
```
## 创建和初始化
**原理**使用make创建map指定初始容量可以提高性能。也可以使用字面量初始化。
```go
// 使用make创建
m1 := make(map[string]int) // 空map
m2 := make(map[string]int, 10) // 指定初始容量
// 字面量初始化
m3 := map[string]int{
"apple": 5,
"banana": 3,
}
fmt.Println(m3) // map[apple:5 banana:3]
```
## 访问元素
**原理**:通过键访问值,如果键不存在,返回零值。可以使用第二个返回值检查键是否存在。
```go
m := map[string]int{"a": 1, "b": 2}
value := m["a"] // 1
fmt.Println(value)
// 检查键是否存在
if val, exists := m["c"]; exists {
fmt.Println("存在:", val)
} else {
fmt.Println("不存在")
}
```
## 修改和添加元素
**原理**:直接赋值键即可添加或修改。如果键存在则修改,不存在则添加。
```go
m := make(map[string]int)
m["key1"] = 10 // 添加
m["key1"] = 20 // 修改
fmt.Println(m) // map[key1:20]
```
## 删除元素
**原理**使用delete函数删除键值对。如果键不存在delete不报错。
```go
m := map[string]int{"a": 1, "b": 2, "c": 3}
delete(m, "b") // 删除键"b"
fmt.Println(m) // map[a:1 c:3]
delete(m, "d") // 删除不存在的键,无错误
```
## 遍历map
**原理**使用range遍历键值对。遍历顺序是随机的不能依赖顺序。
```go
m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Printf("key: %s, value: %d\n", key, value)
}
// 只遍历键
for key := range m {
fmt.Println(key)
}
```
## map是引用类型
**原理**map是引用类型赋值时共享底层数据。修改一个会影响另一个。
```go
m1 := map[string]int{"a": 1}
m2 := m1 // 共享底层数据
m2["b"] = 2
fmt.Println(m1) // map[a:1 b:2] m1也被修改
fmt.Println(m2) // map[a:1 b:2]
```
## map作为函数参数
**原理**map作为参数传递时是引用传递函数内修改会影响调用者。
```go
func addEntry(m map[string]int, key string, value int) {
m[key] = value
}
func main() {
m := make(map[string]int)
addEntry(m, "new", 42)
fmt.Println(m) // map[new:42]
}
```
## 扩展 Map的底层原理
从Go 1.23开始map的底层实现从传统的哈希表改为基于Swiss table的高性能实现大幅提升了性能和内存效率。以下详细讲解其工作原理。
### 1. Swiss Table结构
Swiss table是一种现代的开放寻址哈希表设计具有以下特点
- 每个桶包含一个控制字节数组和数据数组
- 控制字节存储元数据(哈希值的高位、删除标记等)
- 数据与控制字节分离,减少缓存未命中
```
Swiss Table结构示意图
+-------------------+ +-------------------+
| 控制字节 (8字节) | | 数据槽位 (8个) |
| [H1|H2|H3|H4|...]| | [k1,v1] [k2,v2]...|
+-------------------+ +-------------------+
| 桶0: 控制元数据 | --> | 桶0: 键值对数据 |
+-------------------+ +-------------------+
| 桶1: 控制元数据 | --> | 桶1: 键值对数据 |
+-------------------+ +-------------------+
```
### 2. 哈希计算和定位
**改进的哈希计算**
- 使用更快的哈希函数AesHash或类似
- 哈希值分为高位和低位
- 高位用于SIMD比较低位用于定位
```
哈希计算过程:
Key -> hash(key) -> [高7位: SIMD比较] [低位: 位置索引]
```
### 3. 插入和查找过程
**查找优化**
1. 计算哈希值
2. 使用SIMD指令并行比较控制字节
3. 快速定位匹配的槽位
**插入优化**
1. 查找空闲槽位
2. 使用线性探测或二次探测
3. 写入控制字节和数据
```
查找示意图使用SIMD
Hash(Key) = 0xABCD1234
控制字节 = 0xAB (高位)
位置索引 = 0x1234 & 掩码
控制字节数组: [AB|CD|EF|00|...]
↑ SIMD比较找到匹配
数据数组: [k1,v1] [k2,v2] [k3,v3] ...
```
### 4. 扩容机制
**渐进式扩容**
- 当负载因子超过阈值时触发扩容
- 新表大小为2的幂次
- 每次操作迁移少量数据
- 支持就地扩容in-place rehashing
```
扩容过程:
旧表: 控制字节 [A|B|C|D] --> 数据 [k1,v1] [k2,v2] [k3,v3] [k4,v4]
新表: 控制字节 [A|0|B|0|C|0|D|0] --> 数据 [k1,v1] [k2,v2] [k3,v3] [k4,v4]
(插入空字节分隔)
```
### 5. 性能优势
**相比传统哈希表**
- **更快的查找**SIMD并行比较减少分支预测失败
- **更好的缓存利用**:控制字节和数据分离
- **更低的内存开销**:更紧凑的布局
- **抗哈希冲突**:更好的分布减少最坏情况
```
性能对比:
操作 | 传统哈希表 | Swiss Table
查找 | O(1) avg | O(1) faster
插入 | O(1) avg | O(1) faster
内存使用 | ~32字节/对 | ~24字节/对
```
## 总结
map是Go中常用的数据结构适合需要快速查找的场景。记住map是无序的键必须可哈希操作是引用传递的。
## 作业
请同学们完成以下map练习题以巩固对Go语言map的理解
1. **创建和初始化map**
创建一个map键为string值为int初始化包含3个键值对并打印map。
2. **访问元素**
给定map检查某个键是否存在如果存在打印值否则打印"不存在"。
3. **修改和添加**
从空map开始添加5个键值对然后修改其中一个值。
4. **删除元素**
删除map中的一个元素并打印删除前后的map。
5. **遍历map**
遍历map计算所有值的和。
6. **函数参数**
编写一个函数接受map作为参数添加一个新键值对。
请将代码写在 `main.go` 文件中,并运行测试。