Map是一種數據結構,是一個集合,用于存儲一系列無序的鍵值對。它基于鍵存儲的,鍵就像一個索引一樣,這也是Map強大的地方,可以快速快速檢索數據,鍵指向與該鍵關聯的值。
## 內部實現
Map是基于 *散列表* 來實現,就是我們常說的 *Hash* 表,所以我們每次迭代Map的時候,打印的Key和Value是無序的,每次迭代的都不一樣,即使按照一定的順序存在也不行。
這種方式的好處在于,存儲的數據越多,索引分布越均勻,所以我們訪問鍵值對的速度也就越快,當然存儲的細節還有很多,大家可以參考Hash相關的知識,這里我們只要記住Map存儲的是無序的鍵值對集合。
## 聲明和初始化
### make函數
~~~
dict := make(map[string]int)
~~~
示例中創建了一個鍵類型為string的,值類型為int的空map。
~~~
dict["張三"] = 43
~~~
存儲了一個Key為張三的,Value為43的鍵值對數據。
### Map字面量
此外還有一種使用map字面量的方式創建和初始化map,對于上面的例子,我們可以同等實現。
~~~
dict := map[string]int{"張三":43}
~~~
使用一個大括號進行初始化,鍵值對通過:分開,如果要同時初始化多個鍵值對,使用逗號分割。
### 創建nil map
>[danger]nil的Map是未初始化的,可以只聲明一個變量,**不分配內存**。
~~~
var dict map[string]int
~~~
這個 map是不能操作存儲鍵值對的,必須要初始化后才可以,比如使用make函數, 為其開啟一塊可以存儲數據的內存,也就是初始化。
~~~
var dict map[string]int
dict = make(map[string]int)
dict["張三"] = 43
fmt.Println(dict)
~~~
>[danger] Map的鍵可以是任何值,鍵的類型可以是內置的類型,也可以是結構類型,但是不管怎么樣,這個鍵可以使用`==`運算符進行比較,所以像切片、函數以及含有切片的結構類型就不能用于Map的鍵了,因為他們具有引用的語義,不可比較。
> 所以可以簡化第一種make進行操作
## 使用Map
Map的使用很簡單,和數組切片差不多,數組切片是使用索引,Map是通過鍵。
### 賦值或更新
~~~
dict := make(map[string]int)
dict["張三"] = 43
~~~
### 判斷鍵是否存在
~~~
age := dict["張三"]
~~~
>[info]在Go Map中,如果我們獲取一個不存在的鍵的值,也是可以的,返回的是值類型的零值,這樣就會導致我們不知道是真的存在一個為零值的鍵值對呢,還是說這個鍵值對就不存在。對此,Map為我們提供了檢測一個鍵值對是否存在的方法。
~~~
age,exists := dict["李四"]
~~~
和獲取鍵的值沒有太大區別,只是多了一個返回值。
* 第一個返回值是鍵的值;
* 第二個返回值標記這個鍵是否存在,這是一個boolean類型的變量
### 刪除
刪除一個Map中的鍵值對,可以使用Go內置的delete函數。
~~~
delete(dict,"張三")
~~~
delete函數接受兩個參數,第一個是要操作的Map,第二個是要刪除的Map的鍵。
>[info]delete函數刪除不存在的鍵也是可以的,只是沒有任何作用。
### 遍歷和排序Map
使用for range風格的循環,和遍歷切片一樣。
~~~
dict := map[string]int{"張三": 43}
for key, value := range dict {
fmt.Println(key, value)
}
~~~
這里的 range 返回兩個值
第一個是Map的鍵
第二個是Map的鍵對應的值。
>[info]這里再次強調,這種遍歷是無序的,也就是鍵值對不會按既定的數據出現,如果想安順序遍歷,可以先對Map中的鍵排序,然后遍歷排序好的鍵,把對應的值取出來,下面看個例子就明白了。
~~~
package main
import (
"sort"
"fmt"
)
func main() {
dict := map[string]int{"王五": 60, "張三": 43}
// 先對 names 的 slice 進行排序
var names []string
for name := range dict {
names = append(names, name)
}
// second method
/*
for name, _ := range dict {
names = append(names, name)
}
*/
sort.Strings(names)
// 按排序遍歷
for _, key := range names {
fmt.Println(key, dict[key])
}
}
~~~
## 在函數間傳遞Map
map 是引用傳遞
~~~
func main() {
dict := map[string]int{"王五": 60, "張三": 43}
modify(dict)
fmt.Println(dict["張三"])
}
func modify(dict map[string]int) {
dict["張三"] = 10
}
~~~
上面這個例子輸出的結果是10,也就是說已經被函數給修改了,可以證明傳遞的并不是一個Map的副本。這個特性和切片是類似的,這樣性能就會更高,因為復制整個Map的代價太大了。
## 和其他語言的差異
訪問的 key 不存在時,仍會返回零值,不能通過 nil 來判斷元素是否存在。