Go 版本:1.21.0
前言
随着 Go 1.21.0 版本的发布,新增了两个实用的泛型工具库:maps 和 slices,它们分别提供了处理映射(map)和切片常见操作的函数,减少了我们重复造轮子的过程,提高开发效率。本文将会对 maps 工具库进行介绍。
准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
Maps
maps 是一个泛型工具库,该库包含了对任何类型都支持的实用函数,函数简介如下表所示:
| 函数 | 函数签名 | 功能 | 
|---|---|---|
| Clone | func Clone[M ~map[K]V, K comparable, V any](m M) M | 该函数返回 m 的一个副本,底层基于浅层克隆去实现,使用普通赋值的方式去设置新的键值对 | 
| Copy | func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) | 复制 src 中的所有键值对到 dst 中,如果 dst 中包含 src 中的任意 key,则该 key 对应的 value 将会被覆盖 | 
| DeleteFunc | func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) | 删除 m 中满足 del 返回为 true 的任何键值对 | 
| Equal | func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool | 判断两个 map 是否包含相同的键值对,内部使用 == 进行比较 | 
| EqualFunc | func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool | 类似 Equal 函数,但通过 eq 函数进行比较值,键仍使用 == 进行比较 | 
Clone
Clone 函数接收一个 m 参数,该函数的功能是返回 m 的副本,底层基于浅层克隆去实现,使用普通赋值的方式去设置新的键值对。
代码示例:
            
            
              go
              
              
            
          
          package main
import (
	"fmt"
	"maps"
)
func main() {
	type Programmer struct {
		Name string
		City string
	}
	m1 := map[string]Programmer{
		"programmer-01": {Name: "陈明勇", City: "深圳"},
		"programmer-02": {Name: "张三", City: "广州"},
	}
	m2 := maps.Clone(m1)
	fmt.Printf("m1: %v\n", m1)
	fmt.Printf("m2: %v\n", m2)
}
        执行结果:
            
            
              css
              
              
            
          
          m1: map[programmer-01:{陈明勇 深圳} programmer-02:{张三 广州}]
m2: map[programmer-01:{陈明勇 深圳} programmer-02:{张三 广州}]
        上述例子中,首先创建一个 map 类型的变量 m1,然后通过 maps.Clone() 函数进行克隆,得到 m2,最后通过打印结果可知 m2 的值和 m1 的值一样。
从函数的功能描述中可知,Clone 函数的原理是浅层克隆,那么修改克隆后的 map 任意 key 的 value 将有可能影响原 map 的 value。
我们来看下下面的例子:
            
            
              go
              
              
            
          
          package main
import (
	"fmt"
	"maps"
)
func main() {
	type Programmer struct {
		Name string
		City string
	}
	m1 := map[string]*Programmer{
		"programmer-01": {Name: "陈明勇", City: "深圳"},
		"programmer-02": {Name: "张三", City: "广州"},
	}
	fmt.Printf("m1: %v, %v\n", *m1["programmer-01"], *m1["programmer-02"])
	m2 := maps.Clone(m1)
	fmt.Printf("m2: %v, %v\n", *m2["programmer-01"], *m2["programmer-02"])
	m2["programmer-02"].City = "海口"
	fmt.Printf("m2 被修改后,m1: %v, %v\n", *m1["programmer-01"], *m1["programmer-02"])
	fmt.Printf("m2 被修改后,m2: %v, %v\n", *m2["programmer-01"], *m2["programmer-02"])
}
        执行结果
            
            
              css
              
              
            
          
          m1: {陈明勇 深圳}, {张三 广州}
m2: {陈明勇 深圳}, {张三 广州}
m2 被修改后,m1: {陈明勇 深圳}, {张三 海口}
m2 被修改后,m2: {陈明勇 深圳}, {张三 海口}
        与前面的示例不同,这个例子中的一个关键区别在于 value 是指针类型。从执行结果可以明显看出,如果 m1 的 value 是指针类型,那么在对克隆后的 m2 中的任意 key 对应的 value 进行修改操作后,都会直接影响到 m1。这是因为 m1 和 m2 共享了同一组指向相同 Programmer 结构体的指针,因此对一个指针的修改会在两个 map 中都可见。
Copy
Copy 函数接收两个 map 参数 dst 和 src,该函数的功能是复制 src 中的所有键值对到 dst 中,如果 dst 中包含 src 中的任意 key,则该 key 对应的 value 将会被覆盖。
代码示例:
            
            
              go
              
              
            
          
          package main
import (
	"fmt"
	"maps"
)
func main() {
	m1 := map[string]string{"Name": "陈明勇", "City": "深圳"}
	m2 := map[string]string{"City": "广州", "Phone": "123456789"}
	maps.Copy(m1, m2)
	fmt.Println(m1)
}
        执行结果:
            
            
              arduino
              
              
            
          
          map[City:广州 Name:陈明勇 Phone:123456789]
        在上述例子中,首先创建了两个 map 变量,分别为 m1 和 m2,然后通过 maps.Copy 函数,将 m2 中的键值对复制到 m1 中,最后打印复制后的结果。
根据结果可知,由于 m1 和 m2 都包含 key → City,因此在执行复制操作后, m1 中的 key → City 对应的 value 值会被覆盖。
DeleteFunc
DeleteFunc 函数接收一个 map 类型的参数 m 和一个函数类型的参数 del。该函数的功能是删除 m 中满足 del 返回为 true 的任何键值对。
代码示例:
            
            
              go
              
              
            
          
          package main
import (
	"fmt"
	"maps"
)
func main() {
	m1 := map[int]string{1: "陈明勇", 2: "张三", 3: "李四", 4: "王五"}
	maps.DeleteFunc(m1, func(k int, v string) bool {
		return k%2 == 0
	})
	fmt.Println(m1)
}
        执行结果:
            
            
              arduino
              
              
            
          
          map[1:陈明勇 3:李四]
        在上述例子中,首先创建了一个 map 变量 m1,使用 int 类型作为学号(key),string 类型作为姓名(value),然后通过 maps.DeleteFunc 删除学号为双数的学生,匿名函数的逻辑是 当学号为双数时,返回 true。
总体来说这个例子相对简单,读者可根据实际应用场景进行使用 DeleteFunc 函数。
Equal
Equal 函数接收两个 map 变量,函数的返回值为 bool 类型。该函数的功能是判断两个 map 是否包含相同的键值对,内部使用 == 进行比较。注意:map 类型的 key 和 value 必须是 comparable 类型。
代码示例:
            
            
              go
              
              
            
          
          package main
import (
	"fmt"
	"maps"
)
func main() {
	m1 := map[int]int{0: 0, 1: 1, 2: 2}
	m2 := map[int]int{0: 0, 1: 1}
	m3 := map[int]int{0: 0, 1: 1, 2: 2}
	fmt.Println(maps.Equal(m1, m2)) // false
	fmt.Println(maps.Equal(m1, m3)) // true
}
        执行结果:
            
            
              arduino
              
              
            
          
          false
true
        上述例子中,首先创建了三个 map 类型变量,分别是 m1、m2 和 m3,然后通过 maps.Equal() 函数,对 m1 和 m2 以及 m1 和 m3 进行等价比较。执行结果与预期一致,m1 和 m3 是相等的,m1 和 m2 不相等。
EqualFunc
EqualFunc 函数类似 Equal 函数,只不过是通过 eq 函数进行比较值,键仍使用 == 进行比较。注意: value 可以为任意类型(any)。
代码示例:
            
            
              go
              
              
            
          
          package main
import (
	"fmt"
	"maps"
)
func main() {
	type User struct {
		Nickname string
		IdCard   string
	}
	m1 := map[int]User{0: {Nickname: "陈明勇", IdCard: "111"}, 1: {Nickname: "张三", IdCard: "222"}}
	m2 := map[int]User{0: {Nickname: "陈明勇", IdCard: "111"}}
	m3 := map[int]User{0: {Nickname: "Go技术干货", IdCard: "111"}, 1: {Nickname: "张三", IdCard: "222"}}
	fmt.Println(maps.EqualFunc(m1, m2, func(user User, user2 User) bool {
		return user.IdCard == user2.IdCard
	})) // false
	fmt.Println(maps.EqualFunc(m1, m3, func(user User, user2 User) bool {
		return user.IdCard == user2.IdCard
	})) // true
}
        执行结果:
            
            
              arduino
              
              
            
          
          false
true
        上述例子中,首先创建了三个 map 类型变量,分别是 m1、m2 和 m3。这些 map 使用 int 类型作为编号(key),User 类型作为用户信息(value)。
接着,使用 maps.EqualFunc() 函数,对 m1 和 m2 以及 m1 和 m3 进行等价比较,在这个函数中,我们自定义了比较函数 eq,其逻辑是只要两个 User 结构体的 IdCard 相同,就认为它们是同一个人(相等)。执行结果与预期一致,m1 和 m3 是相等的,m1 和 m2 不相等。
小结
本文对 Go 工具库 maps 进行详细介绍,包括其提供的函数 Clone、Copy、DeleteFunc、Equal 和 EqualFunc,并强调了使用这些函数时需要注意的地方。
总的来说,通过使用这些函数,减少了我们重复造轮子的过程,提高开发效率。
你使用 maps 工具库了吗?感受如何,欢迎留言探讨。