什么是 map

map 是 Go 中的一个内置类型,用于存储键值对。让我们以一个有几个员工的创业公司为例。为了简单起见,我们假设所有这些员工的名字都是唯一的。我们正在寻找一个数据结构来存储每个员工的工资。一个 map 将是这个用例的完美数据结构。员工的名字可以是键,工资可以是值。Maps 类似于 Python 等其他语言中的 map (dict)。

如何创建 map

可以通过将键和值的类型传递给 make 函数来创建 map。make(map[type of key]type of value) 是创建 map的语法。

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

上面这行代码创建了一个名为 personSalary 的 map,它具有 string 类型的键和 int 类型值。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. employeeSalary := make(map[string]int)
  7. fmt.Println(employeeSalary)
  8. }

Run in Playground

上面的程序创建了一个名为 employeeSalary 的 map,map 中有 string 键和 int 值。上面的程序将打印。

  1. map[]

由于我们没有给 map 添加任何元素,所以它是空的。

向 map 添加键值对

向 map 添加键值对的语法与数组的语法相同。下面的程序将添加键值对到personSalary map中。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. personSalary := make(map[string]int)
  7. personSalary["steve"] = 12000
  8. personSalary["jamie"] = 15000
  9. personSalary["mike"] = 9000
  10. fmt.Println("personSalary map contents:", personSalary)
  11. }

Run in playground

我们增加了三名员工 steve、jamie 和 mike 以及他们相应的工资。

上面程序输出

  1. employeeSalary map contents: map[steve:12000 jamie:15000 mike:9000]

也可以声明的时候初始化

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. employeeSalary := map[string]int {
  7. "steve": 12000,
  8. "jamie": 15000,
  9. }
  10. personSalary["mike"] = 9000
  11. fmt.Println("personSalary map contents:", personSalary)
  12. }

Run in playground

上面的程序声明了 employeeSalary,并在声明期间向它添加两个元素。然后添加了一个键值为 mike 的元素。程序输出

  1. personSalary map contents: map[steve:12000 jamie:15000 mike:9000]

map 的键类型不只是字符串类型。 所有可比较的类型,如布尔值,整数,浮点数,复数,字符串…也可以是键。甚至用户定义的类型,如结构体,也可以是键。 如果你想了解有关类型的更多信息,请访问 http://golang.org/ref/spec#Comparison_operators

Map 的零值

map 的零值是 nil。如果你试图向一个 nil map 添加元素,会发生运行时的 panic。因此,在添加元素之前,必须对 map 进行初始化。

  1. package main
  2. func main() {
  3. var employeeSalary map[string]int
  4. employeeSalary["steve"] = 12000
  5. }

Run in playground

在上面的程序中,employeeSalary 为 nil,我们试图在 map 中添加一个新的键。程序会出现以下错误

panic: assignment to entry in nil map

访问 map 的键值对


现在我们已经向 map 添加了一些元素,让我们学习如何访问它们。map[key] 是访问map 元素的语法。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. employeeSalary := map[string]int{
  7. "steve": 12000,
  8. "jamie": 15000,
  9. "mike": 9000,
  10. }
  11. employee := "jamie"
  12. salary := employeeSalary[employee]
  13. fmt.Println("Salary of", employee, "is", salary)
  14. }

Run in playground

上面的程序非常简单。检索并输出员工的工资 jamie 。程序输出

  1. Salary of jamie is 15000

如果没有元素会发生什么?map 将返回该元素类型的零值。在 personSalary map中,如果我们试图访问一个不存在的元素,那么将返回 int 类型的零值 0。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. employeeSalary := map[string]int{
  7. "steve": 12000,
  8. "jamie": 15000,
  9. }
  10. fmt.Println("Salary of joe is", employeeSalary["joe"])
  11. }

Run in playground

上述程序的输出为

  1. Salary of joe is 0

上面的程序返回 joe 的工资为 0。我们运行时没有得到任何错误,说明键值 joe 不存在于personSalary map中。

检查键是否存在

在上一节中我们了解到,当一个键不存在时,将返回该类型的零值。当我们想知道键是否真的存在于 map 中时,这并没有帮助。

例如,我们想知道一个键是否存在于 employeeSalary map 中。

  1. value, ok := map[key]

上面的语法用于确定 map 中是否存在特定的键。如果 ok 为真,那么键就是存在的,它的值在变量 value 中存在,否则键就是不存在的。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. personSalary := map[string]int{
  7. "steve": 12000,
  8. "jamie": 15000,
  9. }
  10. personSalary["mike"] = 9000
  11. newEmp := "joe"
  12. value, ok := personSalary[newEmp]
  13. if ok == true {
  14. fmt.Println("Salary of", newEmp, "is", value)
  15. } else {
  16. fmt.Println(newEmp,"not found")
  17. }
  18. }

Run in playground

在上面的程序中第 15 行,因为 map 里木有 joe 键值,ok 为 false。因此程序将输出

  1. joe not found

遍历 map 中的所有元素


for 循环的 range 形式用于遍历 map 的所有元素。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. personSalary := map[string]int{
  7. "steve": 12000,
  8. "jamie": 15000,
  9. }
  10. personSalary["mike"] = 9000
  11. fmt.Println("All items of a map")
  12. for key, value := range personSalary {
  13. fmt.Printf("personSalary[%s] = %d\n", key, value)
  14. }
  15. }

Run in playground

程序输出

  1. All items of a map
  2. personSalary[mike] = 9000
  3. personSalary[steve] = 12000
  4. personSalary[jamie] = 15000

一个要注意的的点是,在使用 for range 时,不能保证每次执行程序时, map 检索值的顺序都是相同的。

删除键值对

delete(map, key) 是从 map 中删除 key 的语法,delete 函数不返回任何值。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. employeeSalary := map[string]int{
  7. "steve": 12000,
  8. "jamie": 15000,
  9. "mike": 9000,
  10. }
  11. fmt.Println("map before deletion", employeeSalary)
  12. delete(employeeSalary, "steve")
  13. fmt.Println("map after deletion", employeeSalary)
  14. }

Run in playground

上面的程序删除键 steve 并输出

  1. map before deletion map[steve:12000 jamie:15000 mike:9000]
  2. map after deletion map[mike:9000 jamie:15000]

如果我们试图删除一个 map 中不存在的键,不会出现运行时错误。

带有结构体的 map


到目前为止,我们只在 map 中存储了员工的工资。如果我们也能在 map 中存储每个员工的国家,那不是更好吗?这可以通过带有结构体的 map 来实现。雇员可以用一个包含字段 salary 和 country 的结构体来表示,它们将用一个字符串键和结构体值存储在 map 中。让我们写一个程序来了解如何做到这一点。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type employee struct {
  6. salary int
  7. country string
  8. }
  9. func main() {
  10. emp1 := employee{
  11. salary: 12000,
  12. country: "USA",
  13. }
  14. emp2 := employee{
  15. salary: 14000,
  16. country: "Canada",
  17. }
  18. emp3 := employee{
  19. salary: 13000,
  20. country: "India",
  21. }
  22. employeeInfo := map[string]employee{
  23. "Steve": emp1,
  24. "Jamie": emp2,
  25. "Mike": emp3,
  26. }
  27. for name, info := range employeeInfo {
  28. fmt.Printf("Employee: %s Salary:$%d Country: %s\n", name, info.salary, info.country)
  29. }
  30. }

Run in playground

在上面的程序中,employee 结构中包含了 salary 和 country 两个字段,我们创建了三个员工 emp1、emp2 和 emp3。

在第 25 行,我们初始化了一个键类型为 string,值类型为 employee 的 map,其中包含我们创建的三个雇员。

第 31 行中对 map 进行迭代,并在下一行中打印员工的详细信息。这个程序将打印,

  1. Employee: Mike Salary:$13000 Country: India
  2. Employee: Steve Salary:$12000 Country: USA
  3. Employee: Jamie Salary:$14000 Country: Canada

Map 的长度

map 的长度可以使用 len 函数来确定。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. personSalary := map[string]int{
  7. "steve": 12000,
  8. "jamie": 15000,
  9. }
  10. personSalary["mike"] = 9000
  11. fmt.Println("length is", len(personSalary))
  12. }

Run in playground

上面程序中的 len(personSalary) 确定 map 的长度。程序输出

  1. length is 2


Map 是引用类型

切片类似, map 也是引用类型。当将 map 分配给一个新变量时,它们都指向相同的内部数据结构。因此,一个 map 改变了值也会影响另外一个 map。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. employeeSalary := map[string]int{
  7. "steve": 12000,
  8. "jamie": 15000,
  9. "mike": 9000,
  10. }
  11. fmt.Println("Original employee salary", employeeSalary)
  12. modified := employeeSalary
  13. modified["mike"] = 18000
  14. fmt.Println("Employee salary changed", employeeSalary)
  15. }

Run in playground

在上面程序的第 14 行,employeeSalary 被赋值给 modified。在下一行中,mike 的 salary 在 modified map 中被改为 18000。现在 mike 的 salary 在 employeeSalary 中也将是 18000。程序输出,

  1. Original person salary map[steve:12000 jamie:15000 mike:9000]
  2. Person salary changed map[steve:12000 jamie:15000 mike:18000]

类似的情况还有将 map 作为参数传递给函数的情况。当函数内部的 map 发生任何更改时,调用者都会看到。

Map 的比较

不能使用 == 运算符来比较 map。== 只能用于检查 map 是否为 nil

  1. package main
  2. func main() {
  3. map1 := map[string]int{
  4. "one": 1,
  5. "two": 2,
  6. }
  7. map2 := map1
  8. if map1 == map2 {
  9. }
  10. }

Run in playground

程序将抛出编译错误 invalid operation: map1 == map2 (map can only be compared to nil)

检查两个 map 是否相等的一种方法是逐个比较每个 map 的元素。我建议你可以写一个程序来验证它们:)。

我已将我们讨论过的所有的知识点都编译成一个程序。你可以从github下载。

原文链接

https://golangbot.com/maps/