原理
我們都知道怎麼使用goLang中的map來儲存索引值對類型的資料,但是它的內部實現是怎麼樣的?
其實map是一種HashMap,表面上看它只有索引值對結構,實際上在儲存索引值對的過程中涉及到了數組和鏈表。HashMap之所以高效,是因為其結合了順序儲存(數組)和鏈式儲存(鏈表)兩種儲存結構。數組是HashMap的主幹,在數組下有有一個類型為鏈表的元素。
這是一個簡單的HashMap的結構圖:
HashMap結構
當我們儲存一個索引值對時,HashMap會首先通過一個雜湊函數將key轉換為數組下標,真正的key-value是儲存在該數組對應的鏈表裡。
HashMap的數組往往是有限的,那當要儲存的索引值對很多數組不夠或者兩個索引值對雜湊運算後的值相同時,不就會有不同的索引值對儲存在同一個數組下嗎?是的,這個就叫做雜湊碰撞。當發生雜湊碰撞時,索引值對就會儲存在該數組對應鏈表的下一個節點上。
儘管這樣,HashMap的操作效率也是很高的。當不存在雜湊碰撞時尋找複雜度為O(1),存在雜湊碰撞時複雜度為O(N)。所以,但從效能上講HashMap中的鏈表出現越少,效能越好;當然,當儲存的索引值對非常多時,從儲存的角度鏈表又能分擔一定的壓力。
代碼實現
KVMap
首先,HashMap儲存的是索引值對,所以需要一個索引值對類型。
//鏈表結構裡資料的資料類型 索引值對typeKVstruct{ KeystringValuestring}
LinkNode
索引值對又是主要儲存在鏈表裡的,所以需要一個鏈表類。
//鏈表結構typeLinkNodestruct{//節點資料Data KV//下一個節點NextNode *LinkNode}//建立只有頭結點的鏈表funcCreateLink()*LinkNode{//頭結點資料為空白 是為了標識這個鏈表還沒有儲存索引值對varlinkNode = &LinkNode{KV{"",""},nil}returnlinkNode}
當發生雜湊碰撞時,索引值對會儲存在建立的鏈表節點上。這裡需要一個添加節點的功能,我們這裡採用尾插法添加節點。
//尾插法添加節點,返回鏈表總長度func(link *LinkNode)AddNode(dataKV) int {varcount=0//找到當前鏈表尾節點tail := linkfor{count+=1iftail.NextNode==nil{break}else{ tail = tail.NextNode} }varnewNode = &LinkNode{data,nil} tail.NextNode= newNodereturncount+1}
HashMap
接下來,就是豬腳HashMap登場了。
//HashMap木桶(數組)的個數constBucketCount =16typeHashMapstruct{//HashMap木桶Buckets [BucketCount]*LinkNode}//建立HashMapfuncCreateHashMap()*HashMap{ myMap := &HashMap{}//為每個元素添加一個鏈表對象fori :=0; i < BucketCount ; i++ { myMap.Buckets[i] = CreateLink() }returnmyMap}
我們需要一個雜湊散列演算法,將key轉化為一個0-BucketCount的整數,作為存放它的數組的下標。這裡這個散列演算法,應儘可能隨機地使新增的索引值對均勻地分布在每個數組下。
一般像go的map和Java的HashMap都會有一個複雜的散列演算法來達到這個目的,我們這裡只是為了講HashMap原理,暫且就用一個簡單的方法來求出下標。
//自訂一個簡單的散列演算法,它可以將不同長度的key散列成0-BucketCount的整數funcHashCode(keystring)int{varsum =0fori :=0; i
往HashMap裡添加索引值對在這裡順便給大家推薦一個架構交流群:617434785,裡面會分享一些資深架構師錄製的視頻錄影:有Spring,MyBatis,Netty源碼分析,高並發、高效能、分布式、微服務架構的原理,JVM效能最佳化這些成為架構師必備的知識體系。還能領取免費的學習資源。相信對於已經工作和遇到技術瓶頸的碼友,在這個群裡會有你需要的內容。
//添加索引值對func(myMap *HashMap)AddKeyValue(keystring, valuestring){//1.將key散列成0-BucketCount的整數作為Map的數組下標varmapIndex = HashCode(key)//2.擷取對應數組頭結點varlink = myMap.Buckets[mapIndex]//3.在此鏈表添加結點iflink.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(keystring)string{//1.將key散列成0-BucketCount的整數作為Map的數組下標varmapIndex = HashCode(key)//2.擷取對應數組頭結點varlink = myMap.Buckets[mapIndex]varvaluestring//遍曆找到key對應的節點head := linkfor{ifhead.Data.Key == key { value = head.Data.Valuebreak}else{ head = head.NextNode } }returnvalue}
Main_test
packagemainimport("chaors.com/LearnGo/BlockchainCryptography/HashMap")funcmain(){ 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原理已經足夠了。