Go语言映射(Map)

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 函数创建

当需要创‌建一个空映射,然后⁢逐步添加元素时,m⁡ake 函数就像准؜备了一个空的容器:

复制代码
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

相关推荐
小镇学者2 小时前
【golang】goland使用多版本go sdk的方法
开发语言·后端·golang
golang学习记2 小时前
[特殊字符] Go Gin 不停机重启指南:让服务在“洗澡搓背”中无缝升级
开发语言·golang·gin
Lupino3 小时前
从 Haskell 到 Go:记一次 RSA 加密协议移植与“字节陷阱”排查实录
go·haskell
teamlet3 小时前
naivemail - golang开发的最简smtp邮件系统
开发语言·后端·golang
moxiaoran57533 小时前
Go语言的数据类型转换
开发语言·后端·golang
海上彼尚3 小时前
Go之路 - 8.go的接口
开发语言·golang·xcode
乐茵lin3 小时前
golang context底层设计探究
开发语言·后端·golang·大学生·设计·context·底层源码
喵了几个咪4 小时前
Go单协程事件调度器:游戏后端的无锁有序与响应时间掌控
开发语言·游戏·golang
阿狸远翔13 小时前
Protobuf 和 protoc-gen-go 详解
开发语言·后端·golang