golang 字典map
當在雜湊表中查詢某個與鍵值對應的元素值時,我們需要先把鍵值作為引數傳給這個雜湊表。雜湊表會先用雜湊函式(hash function) 把鍵值轉換為雜湊值。
雜湊值通常是一個無符號的整數。一個雜湊表會持有一定數量的桶(bucket) ,也可稱之為雜湊桶 ,這些雜湊桶 會均勻地儲存其所屬雜湊表收納的那些鍵-元素對。
因此,雜湊表會先用這個鍵的雜湊值的低幾位去定位到一個雜湊桶,然後再去這個雜湊桶中,查詢這個鍵。由於鍵-元素對總是被捆綁在一起儲存的,所以一旦找到了鍵,就一定能找到對應的元素。
隨後,雜湊表就會把對應的元素值作為結果返回。只要這個鍵-元素對存在於雜湊表中就一定會被查詢到,因為雜湊表增、改、刪鍵-元素對時候的對映過程,跟上面描述的一樣。
也就是說,字典map不會獨立儲存任何鍵的值,但會獨立儲存它們的雜湊值。鍵值跟元素是捆綁一起儲存的。
問題:字典的鍵型別不能是哪些型別?
回答:Go語言字典的鍵型別不可以是函式型別、字典型別和切片型別。
原因:Go語言規範規定,在鍵型別的值之間必須施加操作符==和!=。換句話說,鍵型別的值必須要支援判等操作。由於函式型別、字典型別和切片型別的值並不支援判等操作,所以字典的鍵型別不能是這些型別。
另外,如果鍵的型別是介面型別,那麼鍵值的實際型別也不能是上述三種類型,否則在程式執行過程中會引發panic(執行時恐慌)
demo:
var badMap2 = map[interface{}]int{ "1": 1, []int{2}: 2, //這裡會引起panic 3:3 }
這裡的變數bandMap2的型別是鍵型別為interface{}的字典型別。這樣宣告不會引起Go語言編譯器的檢查報錯,但在執行時(runtime)系統就會發現這裡的問題,丟擲一個panic。
還要注意,如果鍵型別是陣列型別,也要確保陣列的元素型別不是函式型別、字典型別或切片型別。
為什麼鍵型別的值要支援判等操作?
之前說過,Go語言一旦定位到了某一個雜湊桶,那麼就會試圖在這個桶中查詢鍵值。
首先,每個雜湊桶都會把自己包含的所有鍵的雜湊值存起來。Go語言會用被查詢的鍵的雜湊值與這些雜湊值逐個對比,看看是否有相等的。如果沒有,則直接返回結果。
如果有相等的,那麼再用鍵值本身去對比一次。因為不同鍵值的雜湊值可能相同。這個叫雜湊碰撞
只有當鍵的雜湊值跟鍵值都相等時,才說明查詢成功。
問題:應該優先考慮哪些型別作為字典的鍵型別?
回答:快速查詢的鍵。(優先選用數值型別和指標型別。如果非要選擇字串的話,最好對鍵值的長度進行額外的約束)
分析:****“把鍵值轉換為雜湊值”以及“把查詢的鍵值與雜湊桶中的鍵值對比” 這兩個操作比較耗時。
也就是說:求雜湊和判等操作的速度越快,對應的型別就越適合作為鍵型別。
對於所有的基本型別、指標型別,以及陣列型別、結構體型別和介面型別。以雜湊的操作來說,寬度越小的型別速度通常更快。對於布林型別、整數型別、浮點型別、複數型別、和指標型別來說都是如此。對於字串型別,看值具體長度,長度越短雜湊越快。
對結構體型別的值求雜湊實際上就是對它的所有欄位值求雜湊並進行合併,所以關鍵在於各個欄位的型別以及欄位的數量。而對於介面型別,具體的雜湊演算法,則有值的實際型別決定。
不建議用結構體、介面、陣列等高階型別作為字典的鍵型別,不僅僅是因為對它們的值求雜湊,以及判等的速度較慢,更因為它們的值存在變數。對一個數組來說,我可以任意改變其中的元素值,但在變化前後,它代表了兩個不同的鍵值。
問題:在值為nil的字典上執行讀操作會成功麼,寫操作呢?
當我們僅宣告而並不是初始化一個字典型別的變數時,它的值會是nil。
回答:除了新增鍵值對,其他操作都不會引起錯誤。當我們試圖在一個值為nil的字典新增鍵-元素對時,Go語言在執行時會立即丟擲一個panic。