Map是一种数据结构,是一个集合,用于存储一系列无序的键值对。它基于键存储的,键就像一个索引一样,这也是Map强大的地方,可以快速快速检索数据,键指向与该键关联的值。

内部实现

Map是基于 散列表 来实现,就是我们常说的 Hash 表,所以每次迭代Map的时候,打印的Key和Value是无序的,每次迭代的都不一样,即使按照一定的顺序存在也不行。
这种方式的好处在于,存储的数据越多,索引分布越均匀,所以我们访问键值对的速度也就越快,当然存储的细节还有很多,大家可以参考Hash相关的知识,这里我们只要记住Map存储的是无序的键值对集合。

声明和初始化

make函数

  1. dict := make(map[string]int)

示例中创建了一个键类型为string的,值类型为int的空map。

  1. dict["张三"] = 43

存储了一个Key为张三的,Value为43的键值对数据。

Map字面量

此外还有一种使用map字面量的方式创建和初始化map,对于上面的例子,我们可以同等实现。

  1. dict := map[string]int{"张三":43}

使用一个大括号进行初始化,键值对通过:分开,如果要同时初始化多个键值对,使用逗号分割。

创建nil map

nil的Map是未初始化的,可以只声明一个变量,不分配内存

  1. var dict map[string]int

这个 map是不能操作存储键值对的,必须要初始化后才可以,比如使用make函数, 为其开启一块可以存储数据的内存,也就是初始化。

  1. var dict map[string]int
  2. dict = make(map[string]int)
  3. dict["张三"] = 43
  4. fmt.Println(dict)

Map的键可以是任何值,键的类型可以是内置的类型,也可以是结构类型,但是不管怎么样,这个键可以使用==运算符进行比较,所以像切片、函数以及含有切片的结构类型就不能用于Map的键了,因为他们具有引用的语义,不可比较。

使用Map

Map的使用很简单,和数组切片差不多,数组切片是使用索引,Map是通过键。

赋值或更新

  1. dict := make(map[string]int)
  2. dict["张三"] = 43

判断键是否存在

  1. age := dict["张三"]

在Go Map中,如果我们获取一个不存在的键的值,也是可以的,返回的是值类型的零值,这样就会导致我们不知道是真的存在一个为零值的键值对呢,还是说这个键值对就不存在。对此,Map为我们提供了检测一个键值对是否存在的方法。

  1. age, exists := dict["李四"]

和获取键的值没有太大区别,只是多了一个返回值。

  • 第一个返回值是键的值;
  • 第二个返回值标记这个键是否存在,这是一个boolean类型的变量

删除

删除一个Map中的键值对,可以使用Go内置的delete函数。

  1. delete(dict,"张三")

delete函数接受两个参数,第一个是要操作的Map,第二个是要删除的Map的键。

delete函数删除不存在的键也是可以的,只是没有任何作用。

遍历和排序Map

使用for range风格的循环,和遍历切片一样。

  1. dict := map[string]int{"张三": 43}
  2. for key, value := range dict {
  3. fmt.Println(key, value)
  4. }

这里的 range 返回两个值
第一个是Map的键
第二个是Map的键对应的值。

这里再次强调,这种遍历是无序的,也就是键值对不会按既定的数据出现,如果想安顺序遍历,可以先对Map中的键排序,然后遍历排序好的键,把对应的值取出来,下面看个例子就明白了。

  1. package main
  2. import (
  3. "sort"
  4. "fmt"
  5. )
  6. func main() {
  7. dict := map[string]int{"王五": 60, "张三": 43}
  8. // 先对 names 的 slice 进行排序
  9. var names []string
  10. for name := range dict {
  11. names = append(names, name)
  12. }
  13. // second method,当range 返回的第二个参数不需要使用时,可以舍弃,还是推荐第一种写法
  14. /*
  15. for name, _ := range dict {
  16. names = append(names, name)
  17. }
  18. */
  19. sort.Strings(names)
  20. // 按排序遍历
  21. for _, key := range names {
  22. fmt.Println(key, dict[key])
  23. }
  24. }

在函数间传递Map

map 是值传递

  1. func main() {
  2. dict := map[string]int{"王五": 60, "张三": 43}
  3. modify(dict)
  4. fmt.Println(dict["张三"])
  5. }
  6. func modify(dict map[string]int) {
  7. dict["张三"] = 10
  8. }
  9. // out
  10. 10

上面这个例子输出的结果是10,也就是说已经被函数给修改了,可以证明传递的并不是一个Map的副本。这个特性和切片是类似的,这样性能就会更高,因为复制整个Map的代价太大了。

和其他语言的差异

访问的 key 不存在时,仍会返回零值,不能通过 nil 来判断元素是否存在。

工厂函数

顾名思义,就好比一个工厂一样,可以批量制造某种类型的东西。其实说白了就是封装了个方法减少重复工作。
示例,通过map实现的计算平方,立方

  1. mapx := map[int]func(n int) int{}
  2. mapx[1] = func(n int) int { return n }
  3. mapx[2] = func(n int) int { return n * n }
  4. mapx[3] = func(n int) int { return n * n * n }
  5. fmt.Println(mapx[1](2))
  6. fmt.Println(mapx[2](2))
  7. fmt.Println(mapx[3](2))

示例,go 实现 set 集合

  1. // add
  2. set["name"] = true
  3. set["age"] = true
  4. set["gender"] = true
  5. // loop
  6. for k := range set {
  7. fmt.Println(k)
  8. }
  9. // delete
  10. delete(set, "name")
  11. // exist
  12. _, exist := set["age"]
  13. if exist == true {
  14. fmt.Println("Exist")
  15. } else {
  16. fmt.Println("Not Exist")
  17. }
  18. // len
  19. fmt.Println(len(set))