feat(map): 添加map基础概念和使用示例

- 新增map数据结构的基础概念介绍
- 添加切片与map对比的示例代码和说明
- 实现person查找的slice vs map性能对比示例
- 添加map声明、初始化、访问、删除、遍历等完整操作示例
- 补充map作为引用类型的说明和示意图
- 新增使用map进行去重的功能说明
- 添加slices_vs_map.drawio图表文件
- 更新练习题目,增加去重相关的练习

该提交完整介绍了Go语言中map的核心概念和实际应用,
帮助理解map相比切片在查找效率上的优势。
```
This commit is contained in:
yumaojun03 2026-01-11 18:09:27 +08:00
parent 2ba0062910
commit f24a12dfd6
6 changed files with 384 additions and 3 deletions

View File

@ -2,6 +2,56 @@
Go中的map是一种无序的键值对集合底层基于哈希表实现。每个键必须是可比较的类型值可以是任意类型。map提供快速的查找、插入和删除操作。 Go中的map是一种无序的键值对集合底层基于哈希表实现。每个键必须是可比较的类型值可以是任意类型。map提供快速的查找、插入和删除操作。
为啥有了切片我还有有map
1. 切片如何寻找到指定的对象
```go
func main() {
personList := []Person{}
personList = append(personList, Person{
Name: "张三",
Age: 18,
})
personList = append(personList, Person{
Name: "李四",
Age: 20,
})
// 如何找到李四这个对象
for _, person := range personList {
if person.Name == "李四" {
// 找到了
fmt.Println("找到李四:", person)
break
}
}
// 如果我这个切切片非常大, 上百万, 上千万
// 通过切片查找一个元素, 效率非常低, O(n)
// 这时可以使用 map 来优化查找效率
// 使用map 来存储人员信息, key 是姓名, value 是 Person 对象
personMap := map[string]Person{}
personMap["张三"] = Person{
Name: "张三",
Age: 22,
}
personMap["李四"] = Person{
Name: "李四",
Age: 24,
}
fmt.Println("通过Map直接找到李四: ", personMap["李四"])
}
type Person struct {
Name string
Age int
}
```
![alt text](image.png)
## 核心概念 ## 核心概念
map的底层实现是哈希表通过键的哈希值快速定位值。键必须是可哈希的类型如int、string、指针等值可以是任意类型。map是引用类型赋值时共享底层数据。 map的底层实现是哈希表通过键的哈希值快速定位值。键必须是可哈希的类型如int、string、指针等值可以是任意类型。map是引用类型赋值时共享底层数据。
@ -11,6 +61,9 @@ map的底层实现是哈希表通过键的哈希值快速定位值。键必
var m map[string]int // m == nil var m map[string]int // m == nil
``` ```
+ 无序的: (hash)
+ key不允许重复 (hash)
## 创建和初始化 ## 创建和初始化
**原理**使用make创建map指定初始容量可以提高性能。也可以使用字面量初始化。 **原理**使用make创建map指定初始容量可以提高性能。也可以使用字面量初始化。
@ -101,6 +154,8 @@ fmt.Println(m1) // map[a:1 b:2] m1也被修改
fmt.Println(m2) // map[a:1 b:2] fmt.Println(m2) // map[a:1 b:2]
``` ```
![alt text](image-1.png)
## map作为函数参数 ## map作为函数参数
**原理**map作为参数传递时是引用传递函数内修改会影响调用者。 **原理**map作为参数传递时是引用传递函数内修改会影响调用者。
@ -117,6 +172,9 @@ func main() {
} }
``` ```
## 使用map去重
## 扩展 Map的底层原理 ## 扩展 Map的底层原理
从Go 1.23开始map的底层实现从传统的哈希表改为基于Swiss table的高性能实现大幅提升了性能和内存效率。以下详细讲解其工作原理。 从Go 1.23开始map的底层实现从传统的哈希表改为基于Swiss table的高性能实现大幅提升了性能和内存效率。以下详细讲解其工作原理。
@ -233,4 +291,7 @@ map是Go中常用的数据结构适合需要快速查找的场景。记住map
6. **函数参数** 6. **函数参数**
编写一个函数接受map作为参数添加一个新键值对。 编写一个函数接受map作为参数添加一个新键值对。
7. **去重**
给定一个只能的[]string切片使用map来进行去重
请将代码写在 `main.go` 文件中,并运行测试。 请将代码写在 `main.go` 文件中,并运行测试。

BIN
day02/map/image-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
day02/map/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

135
day02/map/main.go Normal file
View File

@ -0,0 +1,135 @@
package main
import "fmt"
var (
globalMap = map[string]string{}
)
func main() {
personList := []Person{}
personList = append(personList, Person{
Name: "张三",
Age: 18,
})
personList = append(personList, Person{
Name: "李四",
Age: 20,
})
// 如何找到李四这个对象
for _, person := range personList {
if person.Name == "李四" {
// 找到了
fmt.Println("找到李四:", person)
break
}
}
// 如果我这个切切片非常大, 上百万, 上千万
// 通过切片查找一个元素, 效率非常低, O(n)
// 这时可以使用 map 来优化查找效率
// 使用map 来存储人员信息, key 是姓名, value 是 Person 对象
personMap := map[string]Person{}
personMap["张三"] = Person{
Name: "张三",
Age: 22,
}
personMap["李四"] = Person{
Name: "李四",
Age: 24,
}
fmt.Println("通过Map直接找到李四: ", personMap["李四"])
/*
1. 声明和初始化
map[keyType]valueType
keyType 必须是可以hash的类型 常见的可以是 string int float64 bool
*/
// 先声明,在初始化
var animalMap map[string]string
// animalMap = make(map[string]string)
// 初始化一个空的 map
animalMap = map[string]string{}
fmt.Println("animalMap:", animalMap)
// 声明的同时初始化(常用, 简短声明用不了的情况, 只能用 var 声明)
var cityMap = map[string]string{
"北京": "北京是中国的首都",
"上海": "上海是中国的经济中心",
"广州": "广州是中国的南大门",
}
fmt.Println("cityMap:", cityMap)
// 简短声明(最常用, 推荐)
cityMap1 := map[string]string{
"北京": "北京是中国的首都",
"上海": "上海是中国的经济中心",
"广州": "广州是中国的南大门",
}
fmt.Println("cityMap:", cityMap1)
/* * 2. 访问元素
value := map[key]
*/
fmt.Println("== 访问元素 ==")
beijingDesc := cityMap1["北京"]
fmt.Println("北京的描述是:", beijingDesc)
/* * 3. 删除元素
delete(map, key)
*/
delete(cityMap1, "广州")
fmt.Println("删除广州后 cityMap1:", cityMap1)
/* * 2. 增加/修改元素
map[key] = value
key 不存在就是新增key 存在就是修改
*/
cityMap1["深圳"] = "深圳是中国的科技创新中心"
fmt.Println("添加深圳后 cityMap1:", cityMap1)
/*
如何判断某个key是否存在
comok断言: value, ok := map[key]
常用场景: 有了后就不修改 没有才添加
*/
value, ok := cityMap1["上海"]
if ok {
fmt.Println("上海存在,描述是:", value)
} else {
fmt.Println("上海不存在")
}
/*
map的遍历
range iterage -> (k/indev/..., v)
for key, value := range map {
}
*/
fmt.Println("== map的遍历 ==")
for key, value := range cityMap1 {
fmt.Printf("%s 的描述是: %s\n", key, value)
}
// 通常只需要 key 的场景, 对象修改
for key := range cityMap1 {
fmt.Printf("%s 的描述是: %s\n", key, cityMap1[key])
}
/*
map 是一种引用类型, 默认值是 nil, 通过指针 指向数据(key value)c存储区(bucket)
*/
fmt.Println("== map 是引用类型 ==")
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]
}
type Person struct {
Name string
Age int
}

View File

@ -0,0 +1,178 @@
<mxfile host="65bd71144e">
<diagram id="UeU4ztOSoA98feXgVeiI" name="第 1 页">
<mxGraphModel dx="1134" dy="610" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="90" y="140" width="670" height="80" as="geometry"/>
</mxCell>
<mxCell id="3" value="张三" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="140" y="160" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="4" value="李四" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="220" y="160" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="5" value="王五" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="300" y="160" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="6" value="..." style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="378.5" y="160" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="7" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="460" y="160" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="8" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="550" y="160" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="9" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="640" y="160" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="10" value="" style="shape=flexArrow;endArrow=classic;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="174.5" y="290" as="sourcePoint"/>
<mxPoint x="174.5" y="230" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="11" value="bucket" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="180" y="475" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="12" value="&lt;span style=&quot;color: rgb(0, 0, 0);&quot;&gt;bucket&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="330" y="475" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="13" value="&lt;span style=&quot;color: rgb(0, 0, 0);&quot;&gt;bucket&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="470" y="475" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="14" value="&lt;span style=&quot;color: rgb(0, 0, 0);&quot;&gt;bucket&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="610" y="470" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="15" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="195" y="445" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="16" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="345" y="440" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="17" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="485" y="440" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="18" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="620" y="440" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="20" value="array" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="40" y="100" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="21" value="bucket" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="40" y="430" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="27" style="edgeStyle=none;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;" parent="1" source="22" target="15" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="22" value="hash func" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="170" y="360" width="520" height="60" as="geometry"/>
</mxCell>
<mxCell id="23" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="410" y="290" width="180" height="50" as="geometry"/>
</mxCell>
<mxCell id="26" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="24" target="22" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="24" value="key" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="430" y="300" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="25" value="value" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="510" y="300" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="28" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="110" y="670" width="180" height="50" as="geometry"/>
</mxCell>
<mxCell id="29" value="key" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="130" y="680" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="30" value="value" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="210" y="680" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="31" value="只需要一次(最多找几次)" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="55" y="320" width="140" height="30" as="geometry"/>
</mxCell>
<mxCell id="32" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="120" y="900" width="640" height="60" as="geometry"/>
</mxCell>
<mxCell id="33" value="map" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="120" y="860" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="34" value="元数据" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="155" y="910" width="90" height="40" as="geometry"/>
</mxCell>
<mxCell id="36" value="多少个key" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="270" y="910" width="90" height="40" as="geometry"/>
</mxCell>
<mxCell id="37" value="..." style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="380" y="910" width="90" height="40" as="geometry"/>
</mxCell>
<mxCell id="55" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="38" target="45">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="38" value="数据存储区的指针" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="500" y="910" width="130" height="40" as="geometry"/>
</mxCell>
<mxCell id="45" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;" vertex="1" parent="1">
<mxGeometry x="140" y="1000" width="610" height="300" as="geometry"/>
</mxCell>
<mxCell id="46" value="bucket" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="220" y="1090" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="47" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="230" y="1050" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="48" value="bucket" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="350" y="1090" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="49" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="360" y="1050" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="50" value="bucket" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="480" y="1090" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="51" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="490" y="1050" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="52" value="bucket" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="600" y="1090" width="90" height="170" as="geometry"/>
</mxCell>
<mxCell id="53" value="hash" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="610" y="1050" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="54" value="&lt;div style=&quot;color: #f8f8f2;background-color: #272822;font-family: &#39;Cascadia Code NF&#39;, Menlo, Monaco, &#39;Courier New&#39;, monospace, Menlo, Monaco, &#39;Courier New&#39;, monospace;font-weight: normal;font-size: 12px;line-height: 18px;white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #a6e22e;font-weight: bold;&quot;&gt;Swiss Table结构&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="text;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="145" y="970" width="130" height="40" as="geometry"/>
</mxCell>
<mxCell id="56" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="120" y="1370" width="640" height="60" as="geometry"/>
</mxCell>
<mxCell id="57" value="map" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="120" y="1330" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="58" value="元数据" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="155" y="1380" width="90" height="40" as="geometry"/>
</mxCell>
<mxCell id="59" value="多少个key" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="270" y="1380" width="90" height="40" as="geometry"/>
</mxCell>
<mxCell id="60" value="..." style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="380" y="1380" width="90" height="40" as="geometry"/>
</mxCell>
<mxCell id="62" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="61" target="45">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="61" value="数据存储区的指针" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="500" y="1380" width="130" height="40" as="geometry"/>
</mxCell>
<mxCell id="63" value="m1" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="30" y="915" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="64" value="m2" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="30" y="1385" width="60" height="30" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -251,14 +251,21 @@ func MySortV2(arr *[]int) {
6. **函数参数** 6. **函数参数**
编写一个函数,接受字符串切片作为参数,将所有字符串转换为大写并返回新切片, 需要实现2个版本 1.一个值版本, 2. 指针版本。 编写一个函数,接受字符串切片作为参数,将所有字符串转换为大写并返回新切片, 需要实现2个版本 1.一个值版本, 2. 指针版本。
7. **问题代码** 7. **问题代码(思考题)**
分析下面2个版本的差异, 给出最优版本 分析下面2个版本的差异, 给出最优版本
```go ```go
requests := []string{"a", "b", "c"} requests := []string{"a", "b", "c"}
func HandleRequest(*[]any{}) func HandleRequest(*[]any{}) {
// boxed 一下,再传递
// 值传递,容器修改到副本,而不是对象本身
handle_one(&obj)
}
func HandleRequest([]*any{}) func HandleRequest([]*any{}) {
// box 给下去就行了
handle_one(obj)
}
``` ```
请将代码写在 `main.go` 文件中,并运行测试。 请将代码写在 `main.go` 文件中,并运行测试。