# 映射(Map)

Golang中存储键值对的数据类型是映射，即map，由于映射这个中文词汇比较生硬，又没有其他更好的中文词，因此后面还是直接用英文map。

#### 什么是map

Golang中map是一种用来存储键值对的数据类型，可以通过**键(key)**&#x6765;**设置**或**读取**对应的**值(value)**。

#### 创建map

可以使用`make`函数来创建一个map，语法如下：

```go
make(map[key_type]value_type)
```

例如我们可以创建一个名为`personSalary`的map来存储大家的薪水：

```go
personSalary := make(map[string]int)
```

该map的key类型为`string`，value类型为`int`。

map数据类型对应的**零值**为`nil`。如果尝试向值为`nil`的map中添加元素会报错。因此一般情况下需要通过`make`函数来初始化一个空map。

```go
package main

import "fmt"

func main() {
    var personSalary map[string]int
    if personSalary == nil {
        fmt.Println("map is nil. Going to make one.")
        personSalary = make(map[string]int)
    }
}
```

[Go Playground在线运行](https://play.golang.org/p/d-RoQHTmLq1) 这里map `personSalary`值为nil，因此我们使用`make`函数创建了一个map并赋值给了`personSalary`。执行结果如下：

```
map is nil. Going to make one.
```

#### 添加元素

向map内添加元素的语法跟数组添加元素一致。下面的程序向map `personSalary`内添加了几项元素：

```go
package main

import "fmt"

func main() {
    personSalary := make(map[string]int)
    personSalary["steve"] = 12000
    personSalary["jamie"] = 15000
    personSalary["mike"] = 9000
    fmt.Println("personSalary map contents:", personSalary)
}
```

[Go Playground在线运行](https://play.golang.org/p/RNL9qWAM8jP) 该程序执行结果如下：

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

map也可以在声明时同时进行初始化。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    fmt.Println("personSalary map contents:", personSalary)
}
```

[Go Playground在线运行](https://play.golang.org/p/VXQ0gTziDjK) 这里声明了map `personSalary`，并同时初始化了两项元素。然后接着又添加了一个key为`mike`的元素。

Golang中不仅仅是字符串可以做为key，所有**可比较**类型比如布尔、整型、浮点型、复数型等等都可以作为map的key，更多关于**可比较**类型内容可以参考<https://golang.org/ref/spec#Comparison_operators>

#### 访问元素

我们已经了解如何向map中添加元素了，现在我们看下如何访问map元素。访问map元素的语法为`map[key]`。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    employee := "jamie"
    fmt.Println("Salary of", employee, "is", personSalary[employee])
}
```

[Go Playground在线运行](https://play.golang.org/p/h1YT5xqodTY) 该程序非常简单，我们取出了`jamie`(key)的工资(value)并打印出来。结果如下：

```
Salary of jamie is 15000
```

如果我们访问的某个key在map中不存在，map会返回value类型对应的零值。比如对于`personSalary`，如果我们访问的元素不存在，则会返回`int`类型对应的零值`0`。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    fmt.Println("Salary of joe is", personSalary["joe"])
}
```

[Go Playground在线运行](https://play.golang.org/p/F4ZK2K_ytK3) 该程序执行结果如下：

```
Salary of joe is 0
```

虽然map`personSalary`中并没有元素`joe`，但是该程序执行并没有报错，`personSalary["joe"]`返回了值`0`。

那如果我们就是想知道map中是否含有某个`key`怎么办呢？Golang提供了如下方式：

```go
value, ok := map[key]
```

如果`ok`为`true`，表示map中含有该`key`，否则不含有。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    newEmp := "joe"
    salary, ok := personSalary[newEmp]
    if ok == true {
        fmt.Println("Salary of", newEmp, "is", salary)
    } else {
        fmt.Println(newEmp, "not found")
    }
}
```

[Go Playground在线运行](https://play.golang.org/p/MkSqzVxiLJ-) 这里，由于key `joe`不存在，因此`ok`值为false。输出为：

```
joe not found
```

类似数组和切片，我们可以使用`for range`来迭代访问map。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    for key, value := range personSalary {
        fmt.Printf("personSalary[%s] = %d\n", key, value)
    }
}
```

[Go Playground在线运行](https://play.golang.org/p/jl78XdqTQou) 输出如下：

```
personSalary[steve] = 12000
personSalary[jamie] = 15000
```

使用`for range`迭代访问map有一点需要注意，**每次迭代访问map元素的访问次序不保证**。

#### 删除元素

从map中删除元素的语法为`delete(map, key)`。这里`delete`函数调用时无返回值。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    fmt.Println("map before deletion", personSalary)
    delete(personSalary, "steve")
    fmt.Println("map after deletion", personSalary)
}
```

[Go Playground在线运行](https://play.golang.org/p/h2oP12kyavu) 这里删除了`key`为`steve`的元素，输出如下：

```
map before deletion map[jamie:15000 steve:12000]
map after deletion map[jamie:15000]
```

#### 获取map中元素个数

`len`函数可以获取map中元素的个数。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    fmt.Println("length is", len(personSalary))
}
```

[Go Playground在线运行](https://play.golang.org/p/8h1l39SR2xV) 该程序输出为`length is 3`。

#### map是引用类型

Golang中，map和切片(slice)相同，都是引用类型。如果我们把一个map变量赋值给另外一个变量，这两个变量指向同一个底层存储空间，任何一个变量对内部元素进行了修改都会反映到另一个变量上。

```go
package main

import "fmt"

func main() {
    personSalary := map[string]int {
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000   
    fmt.Println("Original person salary", personSalary)
    newPersonSalary := personSalary
    newPersonSalary["mike"] = 18000
    fmt.Println("Person salary changed", personSalary)
}
```

[Go Playground在线运行](https://play.golang.org/p/eB--jeXM3rA) 这里我们将`personSalary`赋值给了一个新变量`newPersonSalary`，然后通过该新变量修改了`mike`的值，由于这两个map指向的底层数据存储空间是一个，因此输出如下：

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

这点在函数传参的时候也会同样试用，需要注意。

#### 判断相等

两个map不能通过`==`运算符进行比较，`==`仅可以用来跟`nil`值进行比较。

```go
package main

func mian() {
    map1 := map[string]int {
        "one": 1,
        "two": 2,
    }

    map2 := map1
    if map1 == map2 {

    }
}
```

[Go Playground在线运行](https://play.golang.org/p/55BaLSpBW-s) 该程序执行会报如下错误：

```
invalid operation: map1 == map2 (map can only be compared to nil)
```

如果我们确实像判断两个map是否相等，可以遍历两个map来判断每个键值对是否相同，以间接判断。
