Go 编程实战:Map——使用 Key-Value 管理键值数据

作者:程序员爱钓鱼日期:2026/8/20

上一篇我们学习了 Slice。Slice 非常适合保存一组动态数据,例如:

1users := []string{"Tom", "Jack", "Lucy"}
2

但是 Slice 主要通过数字下标访问元素:

1users[0]
2users[1]
3

实际开发中,我们经常希望通过用户名、商品编号、配置名称等直接查找数据。例如:

1"Tom" -> 90
2"Jack" -> 85
3"port" -> 8080
4"host" -> localhost
5

这种“一个 Key 对应一个 Value”的数据结构,就是 Go 中的 Map。Map 是 Go 开发中使用频率非常高的数据结构,配置管理、缓存、数据统计、JSON 处理、数据库结果整理等场景都会大量使用。

一、什么是 Map

Map 是一种 Key-Value 键值对数据结构。
基本形式:

1Key -> Value
2

例如:

1Tom  -> 90
2Jack -> 85
3Lucy -> 96
4

如果使用 Slice 保存成绩,可能需要:

1names := []string{"Tom", "Jack", "Lucy"}
2scores := []int{90, 85, 96}
3

而 Map 可以直接表示:

1scores := map[string]int{
2	"Tom":  90,
3	"Jack": 85,
4	"Lucy": 96,
5}
6

查询 Tom 的成绩:

1fmt.Println(scores["Tom"])
2

输出:

190
2

相比数字下标,通过具有业务含义的 Key 查找数据通常更加直观。

二、Map 的基本语法

Map 类型写法:

1map[Key类型]Value类型
2

例如:

1map[string]int
2

表示:

1Key   = string
2Value = int
3

也可以:

1map[int]string
2

表示整数作为 Key,字符串作为 Value。
例如:

1users := map[int]string{
2	1: "Tom",
3	2: "Jack",
4	3: "Lucy",
5}
6

访问:

1fmt.Println(users[2])
2

输出:

1Jack
2

三、定义 Map

可以先声明:

1var scores map[string]int
2

但此时 scoresnil map

1fmt.Println(scores == nil)
2

输出:

1true
2

nil map 可以读取,但是不能直接写入:

1scores["Tom"] = 90
2

这样运行时会发生错误。因此需要先初始化 Map。

四、使用 make 创建 Map

最常见的方式是:

1scores := make(map[string]int)
2

然后添加数据:

1scores["Tom"] = 90
2scores["Jack"] = 85
3scores["Lucy"] = 96
4

完整示例:

1package main
2import "fmt"
3func main() {
4	scores := make(map[string]int)
5	scores["Tom"] = 90
6	scores["Jack"] = 85
7	scores["Lucy"] = 96
8	fmt.Println(scores)
9}
10

还可以给 make 提供一个初始容量提示:

1scores := make(map[string]int, 100)
2

如果预计需要保存较多数据,这种方式可以减少运行过程中重新分配内部存储的开销。不过这个数字不是固定长度,Map 仍然可以继续增加元素。

五、创建 Map 时直接初始化

如果数据已经确定,可以直接:

1scores := map[string]int{
2	"Tom":  90,
3	"Jack": 85,
4	"Lucy": 96,
5}
6

字符串 Map:

1config := map[string]string{
2	"host": "localhost",
3	"port": "8080",
4	"mode": "debug",
5}
6

访问:

1fmt.Println(config["host"])
2

输出:

1localhost
2

六、添加和修改数据

Map 添加数据非常简单:

1scores := make(map[string]int)
2scores["Tom"] = 90
3

如果 Key 不存在,就是新增。
继续:

1scores["Jack"] = 85
2

如果 Key 已经存在:

1scores["Tom"] = 100
2

就是修改。
因此 Map 添加和修改使用相同语法:

1m[key] = value
2

Map 中的 Key 不能重复,同一个 Key 只能对应一个当前 Value。

七、读取 Map 数据

读取数据:

1scores := map[string]int{
2	"Tom": 90,
3}
4fmt.Println(scores["Tom"])
5

输出:

190
2

但有一个非常重要的问题。如果访问不存在的 Key:

1fmt.Println(scores["Jack"])
2

不会直接报错,而是返回 Value 类型的零值。
由于 Value 是 int,因此得到:

10
2

如果 Value 是字符串:

1config := map[string]string{}
2fmt.Println(config["host"])
3

得到空字符串。
因此仅根据返回值,有时候无法判断 Key 到底存在还是不存在。

八、判断 Key 是否存在

Go 提供了非常经典的 Map 查询写法:

1value, ok := scores["Tom"]
2

其中:

1value = 对应的数据
2ok    = Key 是否存在
3

例如:

1score, ok := scores["Tom"]
2if ok {
3	fmt.Println("成绩:", score)
4} else {
5	fmt.Println("用户不存在")
6}
7

也可以直接:

1if score, ok := scores["Tom"]; ok {
2	fmt.Println(score)
3}
4

这是 Go 项目中非常常见的写法。
例如:

1scores := map[string]int{
2	"Tom": 0,
3}
4

如果只执行:

1score := scores["Tom"]
2

得到 0,但无法判断是 Tom 的成绩真的为 0,还是 Tom 不存在。
使用:

1score, ok := scores["Tom"]
2

就可以准确区分。

九、删除 Map 数据

Go 提供内置函数:

1delete()
2

例如:

1scores := map[string]int{
2	"Tom":  90,
3	"Jack": 85,
4}
5delete(scores, "Tom")
6fmt.Println(scores)
7

Tom 对应的数据就被删除了。
基本语法:

1delete(map变量, key)
2

如果删除一个不存在的 Key:

1delete(scores, "Lucy")
2

也不会报错。

十、获取 Map 元素数量

可以使用:

1len()
2

例如:

1scores := map[string]int{
2	"Tom":  90,
3	"Jack": 85,
4	"Lucy": 96,
5}
6fmt.Println(len(scores))
7

输出:

13
2

添加:

1scores["Bob"] = 88
2

此时:

1len(scores)
2

就是 4。
删除:

1delete(scores, "Tom")
2

长度又会减少。

十一、遍历 Map

Map 通常使用 range 遍历:

1scores := map[string]int{
2	"Tom":  90,
3	"Jack": 85,
4	"Lucy": 96,
5}
6for key, value := range scores {
7	fmt.Println(key, value)
8}
9

如果只需要 Key:

1for key := range scores {
2	fmt.Println(key)
3}
4

如果只需要 Value:

1for _, value := range scores {
2	fmt.Println(value)
3}
4

需要特别注意:不要依赖 Map 的遍历顺序。
不能认为:

1for key, value := range scores {
2	// 每次都会按照插入顺序执行
3}
4

Go 不保证 Map 的遍历顺序。如果业务要求固定顺序,通常需要单独保存 Key,然后排序。

十二、按照 Key 排序输出

例如:

1scores := map[string]int{
2	"Tom":  90,
3	"Jack": 85,
4	"Lucy": 96,
5}
6

先提取所有 Key:

1keys := make([]string, 0, len(scores))
2for key := range scores {
3	keys = append(keys, key)
4}
5

然后排序:

1sort.Strings(keys)
2

最后按照排序后的 Key 访问:

1for _, key := range keys {
2	fmt.Println(key, scores[key])
3}
4

完整代码:

1package main
2import (
3	"fmt"
4	"sort"
5)
6func main() {
7	scores := map[string]int{
8		"Tom":  90,
9		"Jack": 85,
10		"Lucy": 96,
11	}
12	keys := make([]string, 0, len(scores))
13	for key := range scores {
14		keys = append(keys, key)
15	}
16	sort.Strings(keys)
17	for _, key := range keys {
18		fmt.Println(key, scores[key])
19	}
20}
21

这也是 Slice 和 Map 配合使用的典型场景。

十三、Map 的 Key 有什么要求

并不是所有类型都能作为 Map 的 Key。
Map 的 Key 必须是可以使用 ==!= 比较的类型。
常见可以作为 Key 的类型包括:

1string
2int
3bool
4数组
5指针
6部分 struct
7

例如:

1map[string]int
2map[int]string
3

都是非常常见的。
Slice 不能直接作为 Map Key:

1map[[]int]string
2

这是错误的,因为 Slice 不能直接使用 == 比较两个切片的内容。
Map 本身和函数类型也不能作为 Map Key。

十四、Map 的 Value 可以很复杂

Map 的 Value 不仅可以是基本类型,还可以是 Slice、Map、Struct 等。
例如 Value 是 Slice:

1users := map[string][]string{
2	"admin": {"Tom", "Jack"},
3	"user":  {"Lucy", "Bob"},
4}
5

访问:

1fmt.Println(users["admin"])
2

输出:

1[Tom Jack]
2

也可以追加:

1users["admin"] = append(users["admin"], "Mike")
2

这种 map[string][]string 在实际开发中非常常见。

十五、嵌套 Map

Map 的 Value 还可以继续是 Map:

1users := map[string]map[string]string{
2	"1001": {
3		"name": "Tom",
4		"age":  "20",
5	},
6	"1002": {
7		"name": "Jack",
8		"age":  "25",
9	},
10}
11

访问:

1fmt.Println(users["1001"]["name"])
2

输出:

1Tom
2

不过当数据结构越来越复杂时,通常更推荐使用 Struct,而不是无限嵌套 Map,因为 Struct 类型更加明确,也更容易维护。

十六、Map 作为函数参数

Map 可以直接作为函数参数:

1func change(scores map[string]int) {
2	scores["Tom"] = 100
3}
4

调用:

1scores := map[string]int{
2	"Tom": 90,
3}
4change(scores)
5fmt.Println(scores["Tom"])
6

输出:

1100
2

这说明函数中修改 Map 内容可以影响调用方看到的数据。
因此通常没有必要写:

1func change(scores *map[string]int)
2

直接传:

1func change(scores map[string]int)
2

一般就可以完成对 Map 内容的修改。

十七、Map 不能直接比较

两个 Map 不能直接:

1a == b
2

例如:

1a := map[string]int{"Tom": 90}
2b := map[string]int{"Tom": 90}
3

下面这样是不允许的:

1fmt.Println(a == b)
2

Map 只能和 nil 比较:

1if a == nil {
2	fmt.Println("nil map")
3}
4

如果需要比较两个 Map 的内容,可以自己遍历比较,或者在合适场景下使用标准库提供的相关工具。

十八、nil Map 和空 Map

下面是 nil Map:

1var a map[string]int
2

此时:

1a == nil
2

true
下面是已经初始化但没有数据的 Map:

1b := make(map[string]int)
2

此时:

1b == nil
2

false
两者:

1len(a)
2len(b)
3

都是 0。
最大的区别之一是 nil Map 不能写入:

1a["Tom"] = 90
2

会发生运行时错误。
而:

1b["Tom"] = 90
2

可以正常执行。
因此如果准备向 Map 写入数据,应先使用 make() 或字面量初始化。

十九、Map 实战:统计单词出现次数

Map 非常适合进行数据统计。
例如:

1words := []string{
2	"go", "java", "go", "rust",
3	"go", "java",
4}
5

统计每个单词出现次数:

1package main
2import "fmt"
3func main() {
4	words := []string{
5		"go", "java", "go",
6		"rust", "go", "java",
7	}
8	counts := make(map[string]int)
9	for _, word := range words {
10		counts[word]++
11	}
12	for word, count := range counts {
13		fmt.Println(word, count)
14	}
15}
16

核心代码只有:

1counts[word]++
2

如果 Key 不存在:

1counts[word]
2

默认得到 0,然后执行 ++,第一次就变成 1。
最终数据类似:

1go   -> 3
2java -> 2
3rust -> 1
4

这就是 Map 非常典型的使用方式。

二十、Map 实战:用户信息查询

例如保存用户 ID 和用户名:

1users := map[int]string{
2	1001: "Tom",
3	1002: "Jack",
4	1003: "Lucy",
5}
6

查询:

1id := 1002
2if name, ok := users[id]; ok {
3	fmt.Println("用户:", name)
4} else {
5	fmt.Println("用户不存在")
6}
7

这种结构可以用于缓存、配置映射、状态映射等场景。

二十一、并发使用 Map 要注意

普通 Map 不适合在没有同步保护的情况下进行并发读写。
例如多个 goroutine 同时修改:

1m["count"]++
2

可能产生并发安全问题。
后面学习并发编程时,可以使用:

1sync.Mutex
2

对共享 Map 进行保护,或者根据具体场景使用:

1sync.Map
2

因此现阶段先记住:

1普通 Map 不要随意进行无同步的并发读写。
2

二十二、Slice 和 Map 怎么选择

如果数据主要按照位置保存:

1第0个
2第1个
3第2个
4

通常使用 Slice:

1[]string
2

如果需要根据 Key 查找:

1userID -> user
2name   -> score
3config -> value
4

通常使用 Map:

1map[string]int
2

例如用户列表:

1users := []string{"Tom", "Jack", "Lucy"}
2

适合 Slice。
用户 ID 对应用户名:

1users := map[int]string{
2	1001: "Tom",
3	1002: "Jack",
4}
5

则更加适合 Map。

二十三、Map 常见错误

第一个错误是没有初始化就写入:

1var m map[string]int
2m["Tom"] = 90
3

应该:

1m := make(map[string]int)
2

第二个错误是无法区分零值和 Key 不存在:

1value := m["Tom"]
2

更可靠的方式:

1value, ok := m["Tom"]
2

第三个错误是依赖 Map 遍历顺序:

1for key := range m {
2}
3

Map 不保证遍历顺序。
第四个错误是使用不支持比较的类型作为 Key:

1map[[]int]string
2

Slice 不能作为 Map Key。
第五个错误是多个 goroutine 无保护地同时读写普通 Map,在并发程序中必须特别注意。

二十四、总结

Map 是 Go 中非常重要的键值数据结构。
定义:

1var scores map[string]int
2

创建:

1scores := make(map[string]int)
2

初始化:

1scores := map[string]int{
2	"Tom":  90,
3	"Jack": 85,
4}
5

添加:

1scores["Lucy"] = 96
2

修改:

1scores["Tom"] = 100
2

读取:

1score := scores["Tom"]
2

判断 Key:

1score, ok := scores["Tom"]
2

删除:

1delete(scores, "Tom")
2

长度:

1len(scores)
2

遍历:

1for key, value := range scores {
2	fmt.Println(key, value)
3}
4

学习 Map 重点掌握:

11.Map 使用 Key-Value 保存数据
22.Key 必须是可比较类型
33.读取不存在的 Key 会返回 Value 的零值
44.使用 value, ok 判断 Key 是否存在
55.delete 可以删除指定 Key
66.Map 遍历顺序不固定
77.nil Map 可以读取但不能写入
88.普通 Map 并发读写需要同步保护
9

到这里,我们已经学习了数组 Array、切片 Slice 和 Map,这三种数据结构能够解决大量集合数据存储问题。但前面的内容中还有一个非常重要的问题:当变量传递给函数以后,到底是在操作原来的数据,还是操作一份副本?为什么有时候修改函数参数不会影响外部变量,而 Map、Slice 又表现得有所不同?要真正理解这些问题,就需要掌握 Go 中非常重要的基础概念——指针 Pointer
下一篇:指针 Pointer——理解地址、取址与解引用


Go 编程实战:Map——使用 Key-Value 管理键值数据》 是转载文章,点击查看原文


相关推荐


【计算机毕业设计】基于Hadoop的智慧政务大数据可视化平台设计与实现
xiaotianyuanma2026/8/7

1.系统介绍 随着数字化政务建设的持续推进,传统政务服务存在流程繁琐、信息分散、数据处理效率低等问题,难以满足公众便捷办事与政府高效管理的需求。为实现政务服务智能化、数据管理可视化,本文设计并实现了基于 Hadoop 的智慧政务大数据可视化平台,助力政务服务数字化转型。 平台采用 Java 语言开发,基于 SpringBoot+Vue 前后端分离架构,结合 MySQL 数据库与 Hadoop 大数据框架构建。平台分为用户端与管理员端,用户端提供注册登录、政策推荐、服务预约、在线咨询、数据统计


第13篇:《把PDF变成AI能懂的"密码":我用向量数据库建了个知识库》
第一行代码HW2026/7/29

承上:上一篇我们把文档切成了高质量的小碎片,但它们还只是文本。AI不认识文本,只认识数字。今天,我们要把这些文本碎片变成一串串数字——向量,存入向量数据库,让AI真正能"理解"你的私有知识。 1. 先搞懂:什么是Embedding? 1.1. 用后端老鸟的类比 假设你是数据库管理员,要给1000本书建立索引: 传统索引: "Java编程思想" → 按书名倒排 → J字母开头 → 第3排第5本 向量索引: "Java编程思想" → 转成一串数字[0.23, -0.15, 0.78, ...]


Python 函数式编程:从思想到实践
卷无止境2026/7/21

函数式编程(Functional Programming,FP)是一种把"计算"看作数学函数求值的编程范式——它不像面向对象那样关注"对象状态",而是强调用函数来描述数据的变换过程。Python 并非纯函数式语言,但它对这套思想的支持相当完善,掌握它能让你的代码更简洁、更易测试、更少 bug。下面我们一层一层把这件事讲清楚。 🧭 核心思想:函数式编程在想什么? 函数式编程的哲学核心只有一句话:数据流过一系列纯函数,产生结果,过程中不改变任何外部状态。 这和流水线工厂很像——每道工序只做一件


Kotlin Flow 深入解析:`stateIn()` 的真正核心,其实是 SharingStarted
潜龙勿用之化骨龙2026/7/13

很多 Android 开发者使用 stateIn() 时,代码几乎都是以下这种: stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = UiState.Loading ) 这其实已经是标准写法了。 实际上: stateIn 做的事情只有两件: 1、把冷流升级为热流 2、通过 SharingStarted 控制上游生命周期 这里最关


Claude Sonnet 5 上线:别再让 Claude Code 一律烧 Opus
鲁大猿2026/7/5

Claude Sonnet 5 上线:别再让 Claude Code 一律烧 Opus 6 月 30 日,Anthropic 发布 Claude Sonnet 5。 如果你平时用 Claude Code,这条消息不应该只理解成“又来了一个更强模型”。 它真正影响的是一个更具体的团队决策:以后 Claude Code 到底什么时候用 Sonnet,什么时候用 Opus,什么时候必须让人接管? 我的判断很直接: 不要全员默认 Opus。 也不要一听 Sonnet 5 便宜、上下文长,就把所有 Ag


你好,我叫Token——AI世界里最忙的搬砖工
Kfaino2026/6/27

码农的AI翻身之旅(一) 你好,我叫Token——AI世界里最忙的搬砖工 大家好。 我叫 Token。 别看我名字洋气,其实我就是个打零工的。 AI世界里,所有人都认识ChatGPT,认识DeepSeek,认识Claude,认识Gemini…… 可是,没有几个人认识我。 然而,没有我,他们一句话都说不出来。 我的出生 某一天。 一个程序员打开了ChatGPT。 他说: 帮我写一个Spring Boot项目。 于是,我出生了。 准确来说,不是我一个。 而是一大群兄弟。 因为AI眼里,根本没


一篇看懂 VKE AI Profiling:AI 应用性能分析优化实战
火山引擎Agent社区2026/6/18

你有没有遇到过这样的情况: AI 模型在训练时 GPU 利用率忽高忽低,容器资源明明给够了,训练时长却总是超出预期?或者在推理服务上线后,延迟时不时抖动,重启一下又好了,但问题根本找不到? “我的模型在裸机上跑只要 2 小时,放到容器里怎么变成 3 小时了?” “资源都给了,CPU 和内存也没瓶颈,我不知道还要看什么。” 很多团队在模型效果验证通过后,真正进入上线或规模化使用时,往往会遇到一个共同问题:GPU 看起来很忙,但整体效率并不高;系统投入不少,性能瓶颈却不容易快速定位。 这正是 A


架构视图与文档:C4 模型从入门到实战
ltl2026/6/10

你上次打开团队的架构图是什么时候? 大多数团队都有架构图。Confluence 上躺着一张两年前画的系统拓扑图,Visio 文件在某个共享盘里,PowerPoint 里有几页"技术方案评审"的框线图。问题是:没人信它们。新人入职时看一眼,发现和实际系统对不上,从此再也不看。老人心里有一张"真正的架构图",但那张图只存在于他的脑子里。 这不是个别现象。Simon Brown 在 2018 年的调查中发现,超过半数的开发者认为自己团队的架构文档"基本没用"或"严重过时"(Simon Brown, "


Java MyBatis-Flex 实战指南:从 BaseMapper 到 QueryWrapper 的轻量 ORM 用法
唐青枫2026/6/3

简介 MyBatis-Flex 是一个基于 MyBatis 的增强框架。 它的定位很直接: 保留 MyBatis 写 SQL 的灵活性,同时补上通用 CRUD、链式查询、分页、逻辑删除、乐观锁等常用能力。 普通 MyBatis 项目里,常见代码结构是: Entity Mapper 接口 Mapper XML Service Controller 如果只是做一张表的增删改查,也要写不少重复 SQL。 MyBatis-Flex 的思路是: 简单 CRUD 交给 BaseMapper 复杂条件交给


我用AI一小时撸了个单词学习站,每天自动生成5个单词
修己xj2026/5/26

AI编程热度居高不下,我也一直在用CodeBuddy辅助开发。最近发现了一个有意思的功能——Skill(技能包),灵机一动:不如写个技能,每天自动生成英文单词并部署成网页,这样打开浏览器就能学。 说干就干,全程AI辅助,一个多小时就搞定了。过程分享给大家。 以下是项目地址及在线地址 在线访问:xiuji008.github.io/everyday-en… 项目完全开源:github.com/xiuji008/ev… 灵感来源 CodeBuddy的Skill功能可以定义一套自动化流程。比如我说“

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读