映射是一種數據結構,用於存儲一系列無序的鍵值對,它基於鍵來存儲值。映射的特點是能夠基於鍵快速檢索數據。鍵就像是數組的索引一樣,指向與鍵關聯的值。
與 C++、Java 等編程語言不同,在 Golang 中使用映射不需要引入任何庫。因此 Golang 的映射使用起來更加方便。我們可以通過下圖簡要的理解一下映射中鍵值對的關系:
圖中的每個鍵值對表示一種顏色的字符串名稱及其對應的十六進制值,其中名稱為鍵,十六進制數為值。
映射的實現
映射是一個數據集合,所以可以是使用類似處理數組和切片的方式來迭代映射中的元素。但映射是無序集合,所以即使以同樣的順序保存鍵值對,迭代映射時,元素的順序可能會不一樣。無序的原因是映射的實現使用了哈希表。
Golang 中的映射在底層是用哈希表實現的,在 /usr/local/go/src/runtime/hashmap.go 中可以查看它的實現細節。而 C++ 中的映射則是使用紅黑樹實現的。
創建和初始化映射
Golang 中有很多種方法可以創建並初始化映射,可以使用內置的 make 函數,也可以使用映射字面量。
使用 make 函數聲明映射
// 創建一個映射,鍵的類型是 string,值的類型是 int myMap := make(map[string]int)
使用字面量聲明映射
// 創建一個映射,鍵和值的類型都是 string // 使用兩個鍵值對初始化映射 myMap := map[string]string{"Red": "#da1337", "Orange": "#e95a22"}
創建映射時,更常用的方法是使用映射字面量。映射的初始長度會根據初始化時指定的鍵值對的數量來確定。
映射的鍵可以是任何值
這個值的類型可以是內置的類型,也可以是結構類型,只要這個值可以使用 == 運算符做比較。切片、函數以及包含切片的結構類型,這些類型由於具有引用語義,不能作為映射的鍵,使用這些類型會造成編譯錯誤:
// 使用映射字面量聲明空映射 // 創建一個映射,使用字符串切片作為映射的鍵 myMap := map[[]string]int{}
如果你使用的 IDE 支持語法檢查,就會提示這段代碼有語法錯誤:
如果直接編譯上面的代碼,會得到一個編譯時錯誤:
invalid map key type []string
雖然切片不能作為映射的鍵,但是卻可以作為映射的值,這個在使用一個映射鍵對應一組數據時,會非常有用:
// 聲明一個存儲字符串切片的映射 // 創建一個映射,使用字符串切片作為值 myMap := map[int][]string{}
元素賦值
通過指定適當類型的鍵並給這個鍵賦一個值就完成了映射的鍵值對賦值:
// 創建一個空映射,用來存儲顏色以及顏色對應的十六進制代碼 myColors := map[string]string{} // 將 Red 的代碼加入到映射 myColors["Red"] = "#da1337"
與切片類似,可以通過聲明一個未初始化的映射來創建一個值為 nil 的映射(一般稱為 nil 映射),nil 映射不能用於存儲鍵值對:
// 通過聲明映射創建一個 nil 映射 var myColors map[string]string // 將 Red 的代碼加入到映射 myColors["Red"] = "#da1337"
運行這段代碼會產生一個運行時錯誤:
panic: assignment to entry in nil map
查找與遍歷
測試鍵值是否存在
查找映射里是否存在某個鍵是映射的一個基本操作。這個操作往往需要用戶寫一些邏輯代碼,根據邏輯代碼確定是否完成了某個操作或者映射里是否緩存了某些數據。查找操作也可以用來比較兩個映射,確定哪些鍵值對互相匹配,哪些鍵值對不匹配。
有兩種方法可以檢查鍵值對是否存在,第一種方式是獲取鍵值對中的值以及一個表示這個鍵是否存在的布爾類型標志:
// 獲取鍵 Blue 對應的值 value, exists := myColors["Blue"] // 這個鍵存在嗎? if exists { fmt.Println(value) }
另一中方式是,只返回鍵對應的值,然后通過判斷這個值是不是零值來確定鍵是否存在:
// 獲取鍵 Blue 對應的值 value := myColors["Blue"] // 這個鍵存在嗎? if value != "" { fmt.Println(value) }
顯然,這種方式只能用在映射存儲的值都是非零值的情況下。
注意:在 Golang 中,通過鍵來索引映射時,即便這個鍵不存在也總會返回一個值。在這種情況下,返回的是該值對應的類型的零值。
遍歷映射
和遍歷數組、切片一樣,使用關鍵字 range 可以遍歷映射中的所有值。但對映射來說,range 返回的不是索引和值,而是鍵值對:
// 創建一個映射,存儲顏色以及顏色對應的十六進制代碼 myColors := map[string]string{ "AliceBlue":"#f0f8ff", "Coral":"#ff7F50", "DarkGray":"#a9a9a9", "ForestGreen": "#228b22", } // 顯示映射里的所有顏色 for key, value := range myColors { fmt.Printf("Key: %s Value: %s\n", key, value) }
執行上面的代碼,輸出如下:
Key: AliceBlue Value: #f0f8ff
Key: Coral Value: #ff7F50
Key: DarkGray Value: #a9a9a9
Key: ForestGreen Value: #228b22
刪除映射中的元素
Golang 提供了一個內置的函數 delete() 用於刪除集合中的元素,下面是一個簡單的例子:
delete(myMap, "hello")
上面的代碼將從 myMap 中刪除鍵為 hello 的鍵值對。如果 hello 這個鍵不存在,那么這個調用將什么都不會發生,也不會有什么副作用。但是如果傳入的映射的變量的值為 nil,該調用將導致程序拋出異常(panic)。
還以前面定義的 myColors 映射為例,我們用 delete() 函數刪除其中的 Coral:
// 創建一個映射,存儲顏色以及顏色對應的十六進制代碼 myColors := map[string]string{ "AliceBlue":"#f0f8ff", "Coral":"#ff7F50", "DarkGray":"#a9a9a9", "ForestGreen": "#228b22", } // 刪除鍵為Coral的鍵值對 delete(myColors, "Coral") // 顯示映射里的所有顏色 for key, value := range myColors { fmt.Printf("Key: %s Value: %s\n", key, value) }
執行上面的代碼,發現輸出的結果中已經沒有 Coral 了:
Key: DarkGray Value: #a9a9a9
Key: ForestGreen Value: #228b22
Key: AliceBlue Value: #f0f8ff
在函數間傳遞映射
在函數間傳遞映射並不會制造出該映射的一個副本。實際上,當傳遞映射給一個函數,並對這個映射做了修改時,所有對這個映射的引用都會察覺到這個修改:
package main import "fmt" func main() { // 創建一個映射,存儲顏色以及顏色對應的十六進制代碼 myColors := map[string]string{ "AliceBlue":"#f0f8ff", "Coral":"#ff7F50", "DarkGray":"#a9a9a9", "ForestGreen": "#228b22", } // 顯示映射里的所有顏色 for key, value := range myColors { fmt.Printf("Key: %s Value: %s\n", key, value) } fmt.Println() // 調用函數來移除指定的鍵 removeColor(myColors, "Coral") // 顯示映射里的所有顏色 for key, value := range myColors { fmt.Printf("Key: %s Value: %s\n", key, value) } } // removeColor 將指定映射里的鍵刪除 func removeColor(colors map[string]string, key string) { delete(colors, key) }
運行上面的程序,輸出如下結果:
Key: Coral Value: #ff7F50
Key: DarkGray Value: #a9a9a9
Key: ForestGreen Value: #228b22
Key: AliceBlue Value: #f0f8ff
Key: AliceBlue Value: #f0f8ff
Key: DarkGray Value: #a9a9a9
Key: ForestGreen Value: #228b22
可以看到,在調用了 removeColor 函數后,main 函數中引用的映射中也不再有 Coral 顏色了。這個特性和切片類似,保證可以用很小的成本來復制映射。
總結
映射是 Golang 中內置的保存鍵值對類型數據的類型。從本文的示例中可以看出,Golang 映射實現的簡單易用,並且在函數間傳遞時的性能很好、開銷很低。
參考:
The Go Programming Language Specification
《Go 語言實戰》
《Go語言編程入門與實戰技巧》
Go基礎系列:map類型