Map是Go语言中的一个重要的数据结构,它提供了键值对的存储方式,类似其他语言中的哈希表或字典。
一、映射的基本概念
映射就像现实生活中的各种对应关系:身份证号对应个人信息、手机号对应联系人、商品编号对应商品详情。在编程世界中,映射提供了一种通过键(key )快速查找值(value)的数据结构。
在 Go 语言中,映射的类型表示为 map[KeyType]ValueType,其中
KeyType 是键的类型,
ValueType 是值的类型。
键类型必须是可比较的类型,比如字符串、数字、布尔值,但不能是切片、映射或函数。
二、Map的定义方式
Go 语言提供了多种创建映射的方式,就像烹饪有多种准备食材的方法一样,每种方式都有其适用的场景。
(一) 使用字面量创建
这是最直观的方式,就像一开始就把所有的书按类别摆放在书架上:
func MapDemo1() {
// 课程访问统计映射
site := map[string]int{
"Java": 1200,
"Python": 8000,
"PHP": 500,
"C#": 300,
}
//访问用户映射
userInfo := map[string]interface{}{
"name": "linruxin",
"age": 25,
"email": "linruxin@linruxin.com",
"isVip": true,
"skills": []string{"Go", "Java", "Python"},
}
fmt.Printf("编程导航访问量:%d\n", site["Java"])
fmt.Printf("用户姓名:%s\n", userInfo["name"])
}
输出:
编程导航访问量:1200
用户姓名:linruxin
(二) 使用 make 函数创建
当需要创建一个空映射,然后逐步添加元素时,make 函数就像准备了一个空的容器:
func MapDemo2() {
//创建空映射
inventory := make(map[string]int)
inventory["Java"] = 1200
inventory["Python"] = 8000
inventory["PHP"] = 500
inventory["C#"] = 300
fmt.Printf("当前课程:%v\n", inventory)
// 也可以指定初始容量
cache := make(map[string]string, 100)
cache["token_123"] = "user_data_encrypted"
fmt.Printf("缓存数据:%v\n", cache)
}
输出:
当前课程:map[C#:300 Java:1200 PHP:500 Python:8000]
缓存数据:map[token_123:user_data_encrypted]
(三) 声明后初始化
有时候需要先声明映射变量,稍后再初始化:
func MapDemo3() {
//先声明
var config map[string]string
// 必须先初始化,否则向 nil 映射写入会 panic
config = make(map[string]string)
//使用
config = map[string]string{
"database_host": "localhost",
"database_port": "5432",
"redis_host": "127.0.0.1",
"redis_port": "6379",
}
fmt.Printf("数据库配置:%s:%s\n",
config["database_host"], config["database_port"])
}
输出:
数据库配置:localhost:5432
PS:
如果只是声明map,但没有初始化,只能读,不能写
三、映射的增删改查操作
1. 判断key是否存在映射中
在Go语言中,访问map中不存在的键会返回该类型的零值。因此,我们需要一种方法来判断键是否真实存在
语法:
value, is_exist := map[key]
如果key存在,那is_exist就是true, value是对应的值。
否则is_exist就是false, value是map的value数据类型的零值。
注意 : 如果key不存在,通过**map[key]**访问不会给map自动插入这个新key。C++是会自动插入新key的,两个语言不一样。如果确定key存在,可以直接使用map[key]拿到value。
func MapDemo4() {
// 构造一个map
str := "abc"
dict := map[rune]int{}
for _, value := range str {
dict[value]++
}
fmt.Println(dict)
// 访问map里不存在的key,并不会像C++一样自动往map里插入这个新key
value, ok := dict['z']
fmt.Println(value, ok) // 0 false
fmt.Println(dict) // map[97:1 98:1 99:1]
// 访问map里已有的key
value2 := dict['a']
fmt.Println(value2) // 1
}
输出:
map[97:1 98:1 99:1]
0 false
map[97:1 98:1 99:1]
1
2. 增加和修改元素
在映射中,增加新元素和修改现有元素使用相同的语法:
func MapDemo5() {
// 创建一个空的用户积分映射
userPoints := make(map[string]int)
// 添加新用户
userPoints["鱼皮"] = 1000
userPoints["小明"] = 500
fmt.Printf("初始积分:%v\n", userPoints)
// 修改现有用户积分
userPoints["鱼皮"] = 1500 // 鱼皮积分增加了
userPoints["小红"] = 800 // 添加新用户小红
fmt.Printf("更新后积分:%v\n", userPoints)
// 批量操作的示例
newUsers := map[string]int{
"编程小助手": 300,
"算法大师": 2000,
"代码审查员": 1200,
}
// 将新用户合并到现有映射中
for name, points := range newUsers {
userPoints[name] = points
}
fmt.Printf("合并后积分:%v\n", userPoints)
}
输出:
初始积分:map[小明:500 鱼皮:1000]
更新后积分:map[小明:500 小红:800 鱼皮:1500]
合并后积分:map[代码审查员:1200 小明:500 小红:800 算法大师:2000 编程小助手:300 鱼皮:1500]
3. 查询元素
查询映射中的元素有两种方式:直接访问和安全访问。
func MapDemo6() {
products := map[string]float64{
"MacBook": 9999.99,
"iPhone": 5999.00,
"AirPods": 1299.00,
"AppleWatch": 2499.00,
}
// 直接访问(如果键不存在,返回零值)
price := products["MacBook"]
fmt.Printf("MacBook 价格:%.2f 元\n", price)
// 访问不存在的键
unknownPrice := products["Huawei"]
fmt.Printf("Huawei 价格:%.2f 元\n", unknownPrice) // 输出 0.00
// 检查商品是否在售
checkProduct := func(name string) {
if price, exists := products[name]; exists {
fmt.Printf("✅ %s 在售,价格:%.2f 元\n", name, price)
} else {
fmt.Printf("❌ %s 暂未上架\n", name)
}
}
checkProduct("AirPods")
checkProduct("iPad")
checkProduct("剪切助手VIP")
}
输出:
MacBook 价格:9999.99 元
Huawei 价格:0.00 元
✅ AirPods 在售,价格:1299.00 元
❌ iPad 暂未上架
❌ 剪切助手VIP 暂未上架
4. 删除元素
使用 delete 函数可以删除映射中的元素:
func MapDemo7() {
// 在线用户列表
onlineUsers := map[string]bool{
"编程小助手": true,
"算法新手": true,
"代码大佬": true,
}
fmt.Printf("在线用户:%v\n", onlineUsers)
// 用户下线
delete(onlineUsers, "算法新手")
fmt.Printf("算法新手下线后:%v\n", onlineUsers)
// 删除不存在的键不会报错
delete(onlineUsers, "不存在的用户")
fmt.Printf("删除不存在用户后:%v\n", onlineUsers)
// 批量清理离线用户
offlineUsers := []string{"编程小助手", "代码大佬"}
for _, user := range offlineUsers {
delete(onlineUsers, user)
fmt.Printf("%s 已离线\n", user)
}
fmt.Printf("最终在线用户:%v\n", onlineUsers)
}
输出:
在线用户:map[代码大佬:true 算法新手:true 编程小助手:true]
算法新手下线后:map[代码大佬:true 编程小助手:true]
删除不存在用户后:map[代码大佬:true 编程小助手:true]
编程小助手 已离线
代码大佬 已离线
最终在线用户:map[
四、映射的遍历方法
遍历映射就像逐一检查仓库中的每个货物,Go 语言提供了灵活的遍历方式来满足不同的需求。
(一) 基本遍历方式
func MapDemo8() {
websiteTraffic := map[string]int{
"编程导航": 100000,
"老鱼简历": 60000,
"算法导航": 45000,
"剪切助手": 30000,
}
fmt.Println("=== 网站流量统计 ===")
// 遍历键值对
for site, traffic := range websiteTraffic {
fmt.Printf("%-10s: %6d 访问量\n", site, traffic)
}
fmt.Println("\n=== 只遍历网站名称 ===")
// 只遍历键
for site, _ := range websiteTraffic {
fmt.Printf("- %s\n", site)
}
fmt.Println("\n=== 只遍历访问量 ===")
// 只遍历值
for _, traffic := range websiteTraffic {
fmt.Printf("访问量:%d\n", traffic)
}
}
输出:
=== 网站流量统计 ===
编程导航 : 100000 访问量
老鱼简历 : 60000 访问量
算法导航 : 45000 访问量
剪切助手 : 30000 访问量
=== 只遍历网站名称 ===
-
剪切助手
-
编程导航
-
老鱼简历
-
算法导航
=== 只遍历访问量 ===
访问量:100000
访问量:60000
访问量:45000
访问量:30000
(二) 有序遍历
map 默认是无序的,不管是按照 key 还是按照 value 默认都不排序
如果需要有序遍历,需要先获取键,排序后再遍历:
func MapDemo9() {
studentScores := map[string]int{
"张三": 95,
"李四": 87,
"王五": 92,
"赵六": 78,
"钱七": 89,
}
// 按姓名排序遍历
fmt.Println("=== 按姓名排序 ===")
var names []string
for name, _ := range studentScores {
names = append(names, name)
}
//排序
sort.Strings(names)
for _, name := range names {
fmt.Printf("%s: %d\n", name, studentScores[name])
}
//按分数排序遍历
fmt.Println("\n=== 按分数排序(从高到低)===")
type Student struct {
Name string
Score int
}
var students []Student
for name, score := range studentScores {
students = append(students, Student{name, score})
}
fmt.Printf("%v\n", students)
// 按分数降序排序
sort.Slice(students, func(i, j int) bool {
return students[i].Score > students[j].Score
})
for i, student := range students {
fmt.Printf("第%d名: %s - %d 分\n", i+1, student.Name, student.Score)
}
}
输出:
=== 按姓名排序 ===
张三: 95
李四: 87
王五: 92
赵六: 78
钱七: 89
=== 按分数排序(从高到低)===
{张三 95} {李四 87} {王五 92} {赵六 78} {钱七 89}
第1名: 张三 - 95 分
第2名: 王五 - 92 分
第3名: 钱七 - 89 分
第4名: 李四 - 87 分
第5名: 赵六 - 78 分
五、映射的零值与判断
理解映射的零值行为对于编写健壮的程序至关重要,就像了解汽车在不同路况下的表现一样。
(一) 映射的零值
映射的零值是 nil,对 nil 映射的读取是安全的,但写入会引发 panic:
func MapDemo10() {
var userCache map[string]string
fmt.Printf("映射是否为 nil: %t\n", userCache == nil)
// 读取 nil 映射是安全的,返回零值
value := userCache["key"]
fmt.Printf("从 nil 映射读取值: '%s'\n", value)
// 检查 nil 映射的长度
fmt.Printf("nil 映射长度: %d\n", len(userCache))
// 正确的做法:先初始化
userCache = make(map[string]string)
userCache["user1"] = "linruxin"
fmt.Printf("初始化后的映射: %v\n", userCache)
}
输出:
映射是否为 nil: true
从 nil 映射读取值: ''
nil 映射长度: 0
初始化后的映射: map[user1:linruxin
六、映射作为函数参数
映射作为引用类型,在函数间传递时有特殊的行为特性,理解这一点对于编写正确的程序非常重要。
(一) 映射的引用传递特性
// 更新用户积分
func updateUserPoints(points map[string]int, username string, delta int) {
if currentPoints, exists := points[username]; exists {
points[username] = currentPoints + delta
fmt.Printf("✅ %s 积分更新:%d -> %d\n", username, currentPoints, points[username])
} else {
points[username] = delta
fmt.Printf("🆕 新用户 %s 获得 %d 积分\n", username, delta)
}
}
// 批量重置积分
func resetAllPoints(points map[string]int) {
for username := range points {
points[username] = 0
}
fmt.Println("🔄 所有用户积分已重置")
}
// 安全的复制映射
func copyUserPoints(original map[string]int) map[string]int {
copied := make(map[string]int)
for username, points := range original {
copied[username] = points
}
return copied
}
func MapDemo11() {
userPoints := map[string]int{
"linruxin": 1000,
"编程助手": 500,
"算法新手": 300,
}
fmt.Printf("初始积分:%v\n", userPoints)
// 函数修改会影响原映射
updateUserPoints(userPoints, "linruxin", 200)
updateUserPoints(userPoints, "新用户", 100)
fmt.Printf("更新后积分:%v\n", userPoints)
// 创建副本进行测试操作
testPoints := copyUserPoints(userPoints)
fmt.Printf("副本积分:%v\n", testPoints)
// 重置副本不会影响原数据
resetAllPoints(testPoints)
fmt.Printf("原始积分(未受影响):%v\n", userPoints)
fmt.Printf("副本积分(已重置):%v\n", testPoints)
}
输出:
初始积分:map[linruxin:1000 算法新手:300 编程助手:500]
✅ linruxin 积分更新:1000 -> 1200
🆕 新用户 新用户 获得 100 积分
更新后积分:map[linruxin:1200 新用户:100 算法新手:300 编程助手:500]
副本积分:map[linruxin:1200 新用户:100 算法新手:300 编程助手:500]
🔄 所有用户积分已重置
原始积分(未受影响):map[linruxin:1200 新用户:100 算法新手:300 编程助手:500]
副本积分(已重置):map[linruxin:0 新用户:0 算法新手:0 编程助手:0]
(二) 返回映射的函数
// 统计文本中单词出现次数
func countWords(text string) map[string]int {
wordCount := make(map[string]int)
words := strings.Fields(strings.ToLower(text))
for _, word := range words {
word = strings.Trim(word, ".,!?;:")
if word != "" {
wordCount[word]++
}
}
return wordCount
}
// 获取配置信息
func getAppConfig() map[string]interface{} {
return map[string]interface{}{
"app_name": "lin",
"app_version": "1.0.0",
"debug_mode": true,
"max_users": 10000,
"features": []string{"学习路线", "项目教程", "面试准备"},
}
}
// 过滤高频词
func getHighFrequencyWords(wordCount map[string]int, threshold int) map[string]int {
highFreq := make(map[string]int)
for word, count := range wordCount {
//大于等于阈值
if count >= threshold {
highFreq[word] = count
}
}
return highFreq
}
func MapDemo12() {
// 文本分析示例
article := `编程导航是程序员鱼皮创建的编程学习平台。
在编程导航中,你可以找到学习路线、项目教程和面试准备资源。
编程导航致力于帮助程序员提升技能,成为更好的开发者。`
wordStats := countWords(article)
fmt.Println("=== 词频统计 ===")
for word, count := range wordStats {
fmt.Printf("%-8s: %d 次\n", word, count)
}
// 获取高频词(出现2次及以上)
highFreq := getHighFrequencyWords(wordStats, 2)
fmt.Println("\n=== 高频词汇 ===")
for word, count := range highFreq {
fmt.Printf("%-8s: %d\n", word, count)
}
// 配置管理示例
config := getAppConfig()
fmt.Println("\n=== 应用配置 ===")
for key, value := range config {
fmt.Printf("%-12s: %v\n", key, value)
}
}
输出:
=== 词频统计 ===
编程导航是程序员鱼皮创建的编程学习平台。: 1 次
在编程导航中,你可以找到学习路线、项目教程和面试准备资源。: 1 次
编程导航致力于帮助程序员提升技能,成为更好的开发者。: 1 次
=== 高频词汇 ===
=== 应用配置 ===
app_name : lin
app_version : 1.0.0
debug_mode : true
max_users : 10000
features : [学习路线 项目教程 面试准备]
七、映射的应用场景
映射在实际编程中有着广泛的应用,就像多功能工具箱一样,几乎在每个项目中都能找到它的身影。
(一) 缓存系统
// Cache 简单的内存缓存实现
type Cache struct {
data map[string]interface{}
ttl map[string]time.Time
}
func NewCache() *Cache {
return &Cache{
data: make(map[string]interface{}),
ttl: make(map[string]time.Time),
}
}
// Set 缓存设置
func (c *Cache) Set(key string, value interface{}, duration time.Duration) {
c.data[key] = value
c.ttl[key] = time.Now().Add(duration)
fmt.Printf("✅ 缓存设置:%s\n", key)
}
// Get 缓存获取
func (c *Cache) Get(key string) (interface{}, bool) {
//检查是否过期
if expiry, exists := c.ttl[key]; exists {
//是否已过期
if time.Now().After(expiry) {
//已过期 删除缓存
delete(c.ttl, key)
delete(c.data, key)
fmt.Printf("⏰ 缓存过期:%s\n", key)
return nil, false
}
}
value, exists := c.data[key]
if exists {
fmt.Printf("🎯 缓存命中:%s\n", key)
} else {
fmt.Printf("❌ 缓存未命中:%s\n", key)
}
return value, exists
}
func MapDemo13() {
cache := NewCache()
//设置用户信息缓存
cache.Set("user:linruxin", map[string]interface{}{
"name": "程序员linruxin",
"level": "高级工程师",
"projects": []string{"编程导航", "面试鸭", "老鱼简历"},
}, 5*time.Second)
//设置临时数据
cache.Set("temp:data", "这是一个临时数据", 2*time.Second)
//获取数据
if user, exists := cache.Get("user:linruxin"); exists {
fmt.Printf("用户数据:%v\n", user)
}
//等待一段时间后再获取
time.Sleep(2 * time.Second)
cache.Get("user:linruxin")
cache.Get("temp:data")
}
输出:
✅ 缓存设置:user:linruxin
✅ 缓存设置:temp:data
🎯 缓存命中:user:linruxin
用户数据:map[level:高级工程师 name:程序员linruxin projects:[编程导航 面试鸭 老鱼简历]]
🎯 缓存命中:user:linruxin
⏰ 缓存过期:temp:data
(二) 配置管理
// ConfigManager 配置管理器
type ConfigManager struct {
configs map[string]string
}
func NewConfigManager() *ConfigManager {
return &ConfigManager{
configs: map[string]string{
"database.host": "localhost",
"database.port": "5432",
"database.name": "coding_nav",
"redis.host": "127.0.0.1",
"redis.port": "6379",
"app.debug": "true",
"app.max_users": "10000",
"app.session_timeout": "3600",
},
}
}
func (cm *ConfigManager) Get(key string) string {
return cm.configs[key]
}
func (cm *ConfigManager) GetInt(key string) (int, error) {
value := cm.configs[key]
return strconv.Atoi(value)
}
func (cm *ConfigManager) GetBool(key string) (bool, error) {
value := cm.configs[key]
return strconv.ParseBool(value)
}
func (cm *ConfigManager) Set(key string, value string) {
cm.configs[key] = value
fmt.Printf("🔧 配置更新:%s = %s\n", key, value)
}
func (cm *ConfigManager) GetDatabaseConfig() map[string]string {
dbConfig := make(map[string]string)
for key, value := range cm.configs {
if len(key) > 9 && key[:9] == "database" {
// 移除 "database." 前缀
dbKey := key[9:]
dbConfig[dbKey] = value
}
}
return dbConfig
}
func MapDemo14() {
config := NewConfigManager()
//获取数据库配置
fmt.Println("=== 数据库配置 ===")
dbConfig := config.GetDatabaseConfig()
for key, value := range dbConfig {
fmt.Printf("%s:%s\n", key, value)
}
//获取应用配置
fmt.Println("\n=== 应用配置 ===")
debug, _ := config.GetBool("app.debug")
fmt.Printf("调试模式:%t\n", debug)
if maxUsers, err := config.GetInt("app.max_users"); err == nil {
fmt.Printf("最大用户数: %d\n", maxUsers)
}
if timeout, err := config.GetInt("app.session_timeout"); err == nil {
fmt.Printf("回话超时: %d秒\n", timeout)
}
//动态更新配置
fmt.Println("\n=== 配置更新 ===")
config.Set("app.debug", "false")
config.Set("app.max_users", "20000")
is_debug, _ := config.GetBool("app.debug")
fmt.Printf("更新后调试模式:%t\n", is_debug)
if maxUsers, err := config.GetInt("app.max_users"); err == nil {
fmt.Printf("更新后最大用户数:%d\n", maxUsers)
}
}
输出:
=== 数据库配置 ===
=== 应用配置 ===
调试模式:true
最大用户数: 10000
回话超时: 3600秒
=== 配置更新 ===
🔧 配置更新:app.debug = false
🔧 配置更新:app.max_users = 20000
更新后调试模式:false
更新后最大用户数:20000