這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
業餘時間翻譯,水平很差,如有瑕疵,純屬無能。
原文連結
http://blog.golang.org/go-maps-in-action
go語言中的map實戰
1. 簡介
雜湊表是電腦科學中最重要的資料結構之一。許多雜湊表的實現有著千差萬別的特性,但是總體上他們都提供了快速查詢,添加和刪除功能。go語言提供了內建資料類型map。
2. 聲明和初始化
map的聲明格式如下:
map[KeyType] ValueType
KeyType類型必須是可以比較的,而ValueType可以是任意類型,甚至是另一個map。
以下這個m是一個索引值為string,值為int的雜湊表:
var m map[string]int
雜湊表類型是參考型別,像指標或者切片m指向的值是nil;它沒有指向一個初始化了的雜湊表。一個nil雜湊表在讀的時候,像一個空的雜湊表,但是嘗試向m中寫資料會引發一個運行時panic,所以別那樣做。 使用內建函數make初始化一個雜湊表:
m = make(map[string]int)
make函數申請並初始化了一個雜湊表的資料結構並且返回一個指向這個初始化好了的雜湊表。 雜湊表的資料結構是go本身運行時的一個實現細節,並沒有被語言本身所規定【翻者補充:類似c++不同編譯器如何?虛函數一樣吧】。 文章中只關心雜湊表的使用而非實現。
3. 使用雜湊表
go中的雜湊表的使用方法和其他語言相似,向雜湊表中插入一個鍵為“route”值為66的語句為:
m["route"] = 66
查詢鍵為“route”並且把對應的值賦給新的變數i的語句為:
i := m["route"]
如果查詢的索引值在雜湊表中不存在,將拿到實值型別的“0”值。 以m為例,實值型別為int,則“0”值為0:
j := m["root"]// j == 0
len是返回雜湊表中資料個數的內建函數:
n := len(m)
delete是刪除雜湊表中某一索引值資料的內建函數:
delete(m, "route")
delete函數傳回值為空白,如果索引值不存在則不做任何操作。
使用兩個傳回值的方式可以檢查索引值是否存在:
i, ok := m["route"]
在這個語句中,i被賦值為雜湊表中索引值為“route”的值。如果那個索引值不存在,i被賦值為實值型別中的“0”值。第二個傳回值是布爾類型,如果是true,表明索引值存在,否則不存在。
如果只是檢查索引值是否存在,則第一個傳回值使用底線“_":
_, ok := m["route"]
如果要遍曆一個雜湊表中的內容,使用range關鍵字:
for key, value := range m { fmt.Println("Key:", key, "Value:", value)}
如果要初始化資料,使用雜湊表的字面表示:
commits := map[string]int{ "rsc": 3711, "r": 2138, "gri": 1908, "adg": 912,}
同樣的文法可以初始化一個空的雜湊表,這種用法達到的效果和make一致:
m = map[string]int{}
4. 利用“0”值
在雜湊表中查詢資料,如果索引值不存在,返回一個實值型別的“0”值是很方便的:
It can be convenient that a map retrieval yields a zero value when the key is not present.
例如,一個布爾值的雜湊表可以被用來當做一個set使用(布爾類型的“0”值是false)。下邊這個例子遍曆一個Node的鏈表並且列印他們的值,它使用了一個Node的指標為key的雜湊表去判斷鏈表中是否有環:
type Node struct { Next *Node Value interface{} } var first *Node visited := make(map[*Node]bool) for n := first; n != nil; n = n.Next { if visited[n] { fmt.Println("cycle detected") break } visited[n] = true fmt.Println(n.Value) }
visited[n]運算式如果是真,表明n已經被訪問過了,如果是假,表明還沒有。 這樣就不需要使用兩個傳回值的方式去檢查n是否在map中真的存在;預設的“0”值幫我們做了。
另一個有用的例子是切片的雜湊表。向一個空切片中添加資料會申請一個新的切片所以向一個切片的map中append資料會只佔用一行;不需要檢查key是否存在。在下邊的例子中,切片被用來存放person類型的值,每個Person有一個Name欄位和一個切片,這個例子中建立了一個雜湊表關聯每種物品和一個他所喜歡東西的切片。【做了個倒排?】
type Person struct { Name string Likes []string } var people []*Person likes := make(map[string][]*Person) for _, p := range people { for _, l := range p.Likes { likes[l] = append(likes[l], p) } }
列印喜歡cheese的People:
for _, p := range likes["cheese"] { fmt.Println(p.Name, "likes cheese.") }
列印出喜歡bacon的人數
fmt.Println(len(likes["bacon"]), "people like bacon.")
注意,這裡range函數和len函數都把nil切片看做一個長度為零的切片,即使沒有人喜歡cheese或者bacon,也不會有問題。
5. 鍵的類型
像剛才提過的,鍵的類型必須是可比較的。go語言的spec中準確的定義了這個要求,簡而言之,可以比較的類型包括:布爾,數字,字串,指標,訊息channel,介面類型和任何包含了以上類型的結構體和數組。不在此範圍的類型包括切片,雜湊表和函數;這些類型不能使用 “==” 做比較,也不能被用來做雜湊表的索引值。
很明顯字串,整型和其他基礎類型可以作為雜湊表的鍵。 意想不到的是結構體也可以作為索引值,例如,這個雜湊表的雜湊表可以用來存放不同國家的訪問數。
hits := make(map[string]map[string]int)
這是一個鍵為字串,值為字串到int的雜湊表。最外邊表的每一個索引值是到達內部雜湊表的路徑,每個被嵌套的雜湊表的索引值是一個兩個字母的國家碼。這個運算式可以得到一個澳大利亞人訪問文檔頁面的次數。
n := hits["/doc/"]["au"]
不幸的是這個方法在添加資料的時候很笨重,對每個鍵,需要判斷嵌套的map是否存在,如果有必要的話需要建立:
func add(m map[string]map[string]int, path, country string) { mm, ok := m[path] if !ok { mm = make(map[string]int) m[path] = mm } mm[country]++}add(hits, "/doc/", "au")
另一方面,使用結構體作為鍵的雜湊表可以解除這種複雜性:
type Key struct { Path, Country string}hits := make(map[Key]int)
當一個越南人訪問首頁的時候,一行就可以搞定:
hits[Key{"/", "vn"}]++
查看多少個瑞士人查看了spec頁面的語句也很簡單:
n := hits[Key{"/ref/spec", "ch"}]
6. 並發
雜湊表在有並發的情境並不安全:同時讀寫一個雜湊表的後果是不確定的。如果你需要使用goroutines同時對一個雜湊表做讀寫,對雜湊表的訪問需要通過某種同步機製做協調。一個常用的方法是是使用 sync.RWMutex。
這個語句聲明了一個counter變數,這是一個包含了一個map和sync.RWMutex的匿名結構體。
var counter = struct{ sync.RWMutex m map[string]int}{m: make(map[string]int)}
讀counter前,擷取讀鎖:
counter.RLock()n := counter.m["some_key"]counter.RUnlock()fmt.Println("some_key:", n)
寫counter前,擷取寫鎖
counter.Lock()counter.m["some_key"]++counter.Unlock()
7. 遍曆順序
當使用range迴圈遍曆一個雜湊表的時候,遍曆順序是不保證穩定的。因為Go1版本將map的便利順序隨機化了,如果程式依賴之前實現中的穩定的便利順序的話。【翻者注。。不知道怎麼翻譯】 如果你需要一個穩定的遍曆順序,你必須維護一個獨立的資料結構用來保證這個順序。下面這個例子使用了一個獨立的排序切片,按照索引值順序列印一個 map[int] string資料:
import "sort"var m map[int]stringvar keys []intfor k := range m { keys = append(keys, k)}sort.Ints(keys)for _, k := range keys { fmt.Println("Key:", k, "Value:", m[k])}
By Andrew Gerrand