什么是 map
map 是 Go 中的一个内置类型,用于存储键值对。让我们以一个有几个员工的创业公司为例。为了简单起见,我们假设所有这些员工的名字都是唯一的。我们正在寻找一个数据结构来存储每个员工的工资。一个 map 将是这个用例的完美数据结构。员工的名字可以是键,工资可以是值。Maps 类似于 Python 等其他语言中的 map (dict)。
如何创建 map
可以通过将键和值的类型传递给 make 函数来创建 map。make(map[type of key]type of value) 是创建 map的语法。
personSalary := make(map[string]int)
上面这行代码创建了一个名为 personSalary 的 map,它具有 string 类型的键和 int 类型值。
package mainimport ("fmt")func main() {employeeSalary := make(map[string]int)fmt.Println(employeeSalary)}
上面的程序创建了一个名为 employeeSalary 的 map,map 中有 string 键和 int 值。上面的程序将打印。
map[]
由于我们没有给 map 添加任何元素,所以它是空的。
向 map 添加键值对
向 map 添加键值对的语法与数组的语法相同。下面的程序将添加键值对到personSalary  map中。
package mainimport ("fmt")func main() {personSalary := make(map[string]int)personSalary["steve"] = 12000personSalary["jamie"] = 15000personSalary["mike"] = 9000fmt.Println("personSalary map contents:", personSalary)}
我们增加了三名员工 steve、jamie 和 mike 以及他们相应的工资。
上面程序输出
employeeSalary map contents: map[steve:12000 jamie:15000 mike:9000]
也可以声明的时候初始化
package mainimport ("fmt")func main() {employeeSalary := map[string]int {"steve": 12000,"jamie": 15000,}personSalary["mike"] = 9000fmt.Println("personSalary map contents:", personSalary)}
上面的程序声明了 employeeSalary,并在声明期间向它添加两个元素。然后添加了一个键值为 mike 的元素。程序输出
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 进行初始化。
package mainfunc main() {var employeeSalary map[string]intemployeeSalary["steve"] = 12000}
在上面的程序中,employeeSalary 为 nil,我们试图在 map 中添加一个新的键。程序会出现以下错误
panic: assignment to entry in nil map
访问 map 的键值对
现在我们已经向 map 添加了一些元素,让我们学习如何访问它们。map[key] 是访问map 元素的语法。
package mainimport ("fmt")func main() {employeeSalary := map[string]int{"steve": 12000,"jamie": 15000,"mike": 9000,}employee := "jamie"salary := employeeSalary[employee]fmt.Println("Salary of", employee, "is", salary)}
上面的程序非常简单。检索并输出员工的工资 jamie 。程序输出
Salary of jamie is 15000
如果没有元素会发生什么?map 将返回该元素类型的零值。在 personSalary  map中,如果我们试图访问一个不存在的元素,那么将返回 int 类型的零值 0。
package mainimport ("fmt")func main() {employeeSalary := map[string]int{"steve": 12000,"jamie": 15000,}fmt.Println("Salary of joe is", employeeSalary["joe"])}
上述程序的输出为
Salary of joe is 0
上面的程序返回 joe 的工资为 0。我们运行时没有得到任何错误,说明键值 joe 不存在于personSalary  map中。
检查键是否存在
在上一节中我们了解到,当一个键不存在时,将返回该类型的零值。当我们想知道键是否真的存在于 map 中时,这并没有帮助。
例如,我们想知道一个键是否存在于 employeeSalary map 中。
value, ok := map[key]
上面的语法用于确定 map 中是否存在特定的键。如果 ok 为真,那么键就是存在的,它的值在变量 value 中存在,否则键就是不存在的。
package mainimport ("fmt")func main() {personSalary := map[string]int{"steve": 12000,"jamie": 15000,}personSalary["mike"] = 9000newEmp := "joe"value, ok := personSalary[newEmp]if ok == true {fmt.Println("Salary of", newEmp, "is", value)} else {fmt.Println(newEmp,"not found")}}
在上面的程序中第 15 行,因为 map 里木有 joe 键值,ok 为 false。因此程序将输出
joe not found
遍历 map 中的所有元素
for 循环的 range 形式用于遍历 map 的所有元素。
package mainimport ("fmt")func main() {personSalary := map[string]int{"steve": 12000,"jamie": 15000,}personSalary["mike"] = 9000fmt.Println("All items of a map")for key, value := range personSalary {fmt.Printf("personSalary[%s] = %d\n", key, value)}}
程序输出
All items of a mappersonSalary[mike] = 9000personSalary[steve] = 12000personSalary[jamie] = 15000
一个要注意的的点是,在使用 for range 时,不能保证每次执行程序时, map 检索值的顺序都是相同的。
删除键值对
delete(map, key) 是从 map 中删除 key 的语法,delete 函数不返回任何值。
package mainimport ("fmt")func main() {employeeSalary := map[string]int{"steve": 12000,"jamie": 15000,"mike": 9000,}fmt.Println("map before deletion", employeeSalary)delete(employeeSalary, "steve")fmt.Println("map after deletion", employeeSalary)}
上面的程序删除键 steve 并输出
map before deletion map[steve:12000 jamie:15000 mike:9000]map after deletion map[mike:9000 jamie:15000]
如果我们试图删除一个 map 中不存在的键,不会出现运行时错误。
带有结构体的 map
到目前为止,我们只在 map 中存储了员工的工资。如果我们也能在 map 中存储每个员工的国家,那不是更好吗?这可以通过带有结构体的 map 来实现。雇员可以用一个包含字段 salary 和 country 的结构体来表示,它们将用一个字符串键和结构体值存储在 map 中。让我们写一个程序来了解如何做到这一点。
package mainimport ("fmt")type employee struct {salary intcountry string}func main() {emp1 := employee{salary: 12000,country: "USA",}emp2 := employee{salary: 14000,country: "Canada",}emp3 := employee{salary: 13000,country: "India",}employeeInfo := map[string]employee{"Steve": emp1,"Jamie": emp2,"Mike": emp3,}for name, info := range employeeInfo {fmt.Printf("Employee: %s Salary:$%d Country: %s\n", name, info.salary, info.country)}}
在上面的程序中,employee 结构中包含了 salary 和 country 两个字段,我们创建了三个员工 emp1、emp2 和 emp3。
在第 25 行,我们初始化了一个键类型为 string,值类型为 employee 的 map,其中包含我们创建的三个雇员。
第 31 行中对 map 进行迭代,并在下一行中打印员工的详细信息。这个程序将打印,
Employee: Mike Salary:$13000 Country: IndiaEmployee: Steve Salary:$12000 Country: USAEmployee: Jamie Salary:$14000 Country: Canada
Map 的长度
map 的长度可以使用 len 函数来确定。
package mainimport ("fmt")func main() {personSalary := map[string]int{"steve": 12000,"jamie": 15000,}personSalary["mike"] = 9000fmt.Println("length is", len(personSalary))}
上面程序中的 len(personSalary) 确定 map 的长度。程序输出
length is 2
Map 是引用类型
与切片类似, map 也是引用类型。当将 map 分配给一个新变量时,它们都指向相同的内部数据结构。因此,一个 map 改变了值也会影响另外一个 map。
package mainimport ("fmt")func main() {employeeSalary := map[string]int{"steve": 12000,"jamie": 15000,"mike": 9000,}fmt.Println("Original employee salary", employeeSalary)modified := employeeSalarymodified["mike"] = 18000fmt.Println("Employee salary changed", employeeSalary)}
在上面程序的第 14 行,employeeSalary 被赋值给 modified。在下一行中,mike 的 salary 在 modified map 中被改为 18000。现在 mike 的 salary 在 employeeSalary 中也将是 18000。程序输出,
Original person salary map[steve:12000 jamie:15000 mike:9000]Person salary changed map[steve:12000 jamie:15000 mike:18000]
类似的情况还有将 map 作为参数传递给函数的情况。当函数内部的 map 发生任何更改时,调用者都会看到。
Map 的比较
不能使用 == 运算符来比较 map。== 只能用于检查 map 是否为 nil。
package mainfunc main() {map1 := map[string]int{"one": 1,"two": 2,}map2 := map1if map1 == map2 {}}
程序将抛出编译错误 invalid operation: map1 == map2 (map can only be compared to nil)。
检查两个 map 是否相等的一种方法是逐个比较每个 map 的元素。我建议你可以写一个程序来验证它们:)。
我已将我们讨论过的所有的知识点都编译成一个程序。你可以从github下载。
