原理
我們都知道怎麼使用goLang中的map來儲存索引值對類型的資料,但是它的內部實現是怎麼樣的?
其實map是一種HashMap,表面上看它只有索引值對結構,實際上在儲存索引值對的過程中涉及到了數組和鏈表。HashMap之所以高效,是因為其結合了順序儲存(數組)和鏈式儲存(鏈表)兩種儲存結構。數組是HashMap的主幹,在數組下有有一個類型為鏈表的元素。
這是一個簡單的HashMap的結構圖:
HashMap結構
當我們儲存一個索引值對時,HashMap會首先通過一個雜湊函數將key轉換為數組下標,真正的key-value是儲存在該數組對應的鏈表裡。
HashMap的數組往往是有限的,那當要儲存的索引值對很多數組不夠或者兩個索引值對雜湊運算後的值相同時,不就會有不同的索引值對儲存在同一個數組下嗎?是的,這個就叫做雜湊碰撞。當發生雜湊碰撞時,索引值對就會儲存在該數組對應鏈表的下一個節點上。
儘管這樣,HashMap的操作效率也是很高的。當不存在雜湊碰撞時尋找複雜度為O(1),存在雜湊碰撞時複雜度為O(N)。所以,但從效能上講HashMap中的鏈表出現越少,效能越好;當然,當儲存的索引值對非常多時,從儲存的角度鏈表又能分擔一定的壓力。
代碼實現
KVMap
首先,HashMap儲存的是索引值對,所以需要一個索引值對類型。
//鏈表結構裡資料的資料類型 索引值對type KV struct { Key string Value string}
LinkNode
索引值對又是主要儲存在鏈表裡的,所以需要一個鏈表類。
//鏈表結構type LinkNode struct { //節點資料 Data KV //下一個節點 NextNode *LinkNode}//建立只有頭結點的鏈表func CreateLink() *LinkNode { //頭結點資料為空白 是為了標識這個鏈表還沒有儲存索引值對 var linkNode = &LinkNode{KV{"",""}, nil} return linkNode}
當發生雜湊碰撞時,索引值對會儲存在建立的鏈表節點上。這裡需要一個添加節點的功能,我們這裡採用尾插法添加節點。
//尾插法添加節點,返回鏈表總長度func (link *LinkNode) AddNode(data KV) int { var count = 0 //找到當前鏈表尾節點 tail := link for { count += 1 if tail.NextNode == nil { break }else { tail = tail.NextNode } } var newNode = &LinkNode{data, nil} tail.NextNode = newNode return count+1}
HashMap
接下來,就是豬腳HashMap登場了。
//HashMap木桶(數組)的個數const BucketCount = 16type HashMap struct { //HashMap木桶 Buckets [BucketCount]*LinkNode}//建立HashMapfunc CreateHashMap() *HashMap { myMap := &HashMap{} //為每個元素添加一個鏈表對象 for i := 0; i < BucketCount ; i++ { myMap.Buckets[i] = CreateLink() } return myMap}
我們需要一個雜湊散列演算法,將key轉化為一個0-BucketCount的整數,作為存放它的數組的下標。這裡這個散列演算法,應儘可能隨機地使新增的索引值對均勻地分布在每個數組下。
一般像go的map和Java的HashMap都會有一個複雜的散列演算法來達到這個目的,我們這裡只是為了講HashMap原理,暫且就用一個簡單的方法來求出下標。
//自訂一個簡單的散列演算法,它可以將不同長度的key散列成0-BucketCount的整數func HashCode(key string) int { var sum = 0 for i := 0; i < len(key); i++ { sum += int(key[i]) } return (sum % BucketCount)}
往HashMap裡添加索引值對
//添加索引值對func (myMap *HashMap)AddKeyValue(key string, value string) { //1.將key散列成0-BucketCount的整數作為Map的數組下標 var mapIndex = HashCode(key) //2.擷取對應數組頭結點 var link = myMap.Buckets[mapIndex] //3.在此鏈表添加結點 if link.Data.Key == "" && link.NextNode == nil { //如果當前鏈表只有一個節點,說明之前未有值插入 修改第一個節點的值 即未發生雜湊碰撞 link.Data.Key = key link.Data.Value = value fmt.Printf("node key:%v add to buckets %d first node\n", key, mapIndex) }else { //發生雜湊碰撞 index := link.AddNode(KV{key, value}) fmt.Printf("node key:%v add to buckets %d %dth node\n", key, mapIndex, index) }}
根據鍵從HashMap裡取出對應的值
//按鍵取值func (myMap *HashMap)GetValueForKey(key string) string { //1.將key散列成0-BucketCount的整數作為Map的數組下標 var mapIndex = HashCode(key) //2.擷取對應數組頭結點 var link = myMap.Buckets[mapIndex] var value string //遍曆找到key對應的節點 head := link for { if head.Data.Key == key { value = head.Data.Value break }else { head = head.NextNode } } return value}
Main_test
package mainimport ( "chaors.com/LearnGo/BlockchainCryptography/HashMap")func main() { myMap := HashMap.CreateHashMap() myMap.AddKeyValue("001", "1") myMap.AddKeyValue("002", "2") myMap.AddKeyValue("003", "3") myMap.AddKeyValue("004", "4") myMap.AddKeyValue("005", "5") myMap.AddKeyValue("006", "6") myMap.AddKeyValue("007", "7") myMap.AddKeyValue("008", "8") myMap.AddKeyValue("009", "9") myMap.AddKeyValue("010", "10") myMap.AddKeyValue("011", "11") myMap.AddKeyValue("012", "12") myMap.AddKeyValue("013", "13") myMap.AddKeyValue("012", "14") myMap.AddKeyValue("015", "15")}
RUN
main_test.png
一個簡單的HashMap就實現了,雖然我們的散列演算法只是用了一個簡單的轉換演算法,這對我們理解HashMap原理已經足夠了。
更多技術文章請訪問chaors
.
.
.
.
互連網顛覆世界,區塊鏈顛覆互連網!
--------------------------------------------------20180710 22:43