STL-hasmap源码
HashMap是基于哈希表的Map接口的非同步實現(xiàn)。此實現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵。HashMap儲存的是鍵值對,HashMap很快。此類不保證映射的順序,特別是它不保證該順序恒久不變。
HashMap 內(nèi)部結(jié)構(gòu):可以看作是數(shù)組和鏈表結(jié)合組成的復(fù)合結(jié)構(gòu),數(shù)組被分為一個個桶(bucket),每個桶存儲有一個或多個Entry對象,每個Entry對象包含三部分key(鍵)、value(值),next(指向下一個Entry),通過哈希值決定了Entry對象在這個數(shù)組的尋址;哈希值相同的Entry對象(鍵值對),則以鏈表形式存儲。如果鏈表大小超過樹形轉(zhuǎn)換的閾值(TREEIFY_THRESHOLD= 8),鏈表就會被改造為樹形結(jié)構(gòu)。
hashMap的結(jié)構(gòu)示意圖如下:?
查詢時間復(fù)雜度:HashMap的本質(zhì)可以認為是一個數(shù)組,數(shù)組的每個索引被稱為桶,每個桶里放著一個單鏈表,一個節(jié)點連著一個節(jié)點。很明顯通過下標來檢索數(shù)組元素時間復(fù)雜度為O(1),而且遍歷鏈表的時間復(fù)雜度是O(n),所以在鏈表長度盡可能短的前提下,HashMap的查詢復(fù)雜度接近O(1)
數(shù)組:存儲區(qū)間連續(xù),占用內(nèi)存嚴重,尋址容易,插入刪除困難;
鏈表:存儲區(qū)間離散,占用內(nèi)存比較寬松,尋址困難,插入刪除容易;
Hashmap綜合應(yīng)用了這兩種數(shù)據(jù)結(jié)構(gòu),實現(xiàn)了尋址容易,插入刪除也容易。HashMap的工作原理
HashMap的工作原理 :HashMap是基于散列法(又稱哈希法)的原理,使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當我們給put()方法傳遞鍵和值時,我們先對鍵調(diào)用hashCode()方法,返回的hashCode用于找到bucket(桶)位置來儲存Entry對象。HashMap是在bucket中儲存鍵對象和值對象,作為Map.Entry。并不是僅僅只在bucket中存儲值。HashMap具體的存取過程:·??put存值的方法,過程如下:?
①.判斷鍵值對數(shù)組table[i]是否為空或為null,否則執(zhí)行resize()進行擴容;
②.根據(jù)鍵值key計算hash值得到插入的數(shù)組索引i,如果table[i]==null,直接新建節(jié)點添加,轉(zhuǎn)向⑥,如果table[i]不為空,轉(zhuǎn)向③;
③.判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉(zhuǎn)向④,這里的相同指的是hashCode以及equals;
④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉(zhuǎn)向⑤;
⑤.遍歷table[i],判斷鏈表長度是否大于8,大于8的話把鏈表轉(zhuǎn)換為紅黑樹,在紅黑樹中執(zhí)行插入操作,否則進行鏈表的插入操作;遍歷過程中若發(fā)現(xiàn)key已經(jīng)存在直接覆蓋value即可;
⑥.插入成功后,判斷實際存在的鍵值對數(shù)量size是否超多了最大容量threshold,如果超過,進行擴容。·??get取值的方法,過程如下:
①.指定key 通過hash函數(shù)得到key的hash值
int hash=key.hashCode();
②.調(diào)用內(nèi)部方法 getNode(),得到桶號(一般為hash值對桶數(shù)求模)
int index =hash%Entry[].length;
jdk1.6版本后使用位運算替代模運算,int index=hash&( Entry[].length - 1);
③.比較桶的內(nèi)部元素是否與key相等,若都不相等,則沒有找到。相等,則取出相等記錄的value。
④.如果得到 key 所在的桶的頭結(jié)點恰好是紅黑樹節(jié)點,就調(diào)用紅黑樹節(jié)點的 getTreeNode() 方法,否則就遍歷鏈表節(jié)點。getTreeNode 方法使通過調(diào)用樹形節(jié)點的 find()方法進行查找。由于之前添加時已經(jīng)保證這個樹是有序的,因此查找時基本就是折半查找,效率很高。
⑤.如果對比節(jié)點的哈希值和要查找的哈希值相等,就會判斷 key 是否相等,相等就直接返回;不相等就從子樹中遞歸查找。如何重新調(diào)整HashMap的大小
“如果HashMap的大小超過了負載因子(load factor)定義的容量,怎么辦?”
HashMap的擴容閾值(threshold = capacity* loadFactor 容量范圍是16~2的30次方),就是通過它和size進行比較來判斷是否需要擴容。默認的負載因子大小為0.75,也就是說,當一個map填滿了75%的bucket時候,將會創(chuàng)建原來HashMap大小的兩倍的bucket數(shù)組(jdk1.6,但不超過最大容量),來重新調(diào)整map的大小,并將原來的對象放入新的bucket數(shù)組中。這個過程叫作rehashing,因為它調(diào)用hash方法找到新的bucket位置。 解決 hash 沖突的常見方法
針對哈希表直接定址可能存在hash沖突,舉一個簡單的例子,例如:
第一個鍵值對A進來,通過計算其key的hash得到的index=0。記做:Entry[0] = A。
第二個鍵值對B,通過計算其index也等于0, HashMap會將B.next =A,Entry[0] =B,
第三個鍵值對C,通過計算其index也等于0,那么C.next = B,Entry[0] = C;
這樣我們發(fā)現(xiàn)index=0的地方事實上存取了A,B,C三個鍵值對,它們通過next這個屬性鏈接在一起。對于不同的元素,可能計算出了相同的函數(shù)值,這樣就產(chǎn)生了hash 沖突,那要解決沖突,又有哪些方法呢?具體如下:
a. 鏈地址法:將哈希表的每個單元作為鏈表的頭結(jié)點,所有哈希地址為 i 的元素構(gòu)成一個同義詞鏈表。即發(fā)生沖突時就把該關(guān)鍵字鏈在以該單元為頭結(jié)點的鏈表的尾部。
b. 開放定址法:即發(fā)生沖突時,去尋找下一個空的哈希地址。只要哈希表足夠大,總能找到空的哈希地址。
c. 再哈希法:即發(fā)生沖突時,由其他的函數(shù)再計算一次哈希值。
d. 建立公共溢出區(qū):將哈希表分為基本表和溢出表,發(fā)生沖突時,將沖突的元素放入溢出表。HashMap采用哪種方法解決沖突的呢?
HashMap 就是使用鏈地址法來解決沖突的(jdk8中采用平衡樹來替代鏈表存儲沖突的元素,但hash() 方法原理相同)。當兩個對象的hashcode相同時,它們的bucket位置相同,碰撞就會發(fā)生。此時,可以將 put 進來的 K- V 對象插入到鏈表的尾部。對于儲存在同一個bucket位置的鏈表對象,可通過鍵對象的equals()方法用來找到鍵值對。作者:visant
原文鏈接:https://blog.csdn.net/visant/article/details/80045154
————————————————
版權(quán)聲明:本文為CSDN博主「喝冰紅茶的蟲」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_28864485/article/details/112096466
總結(jié)
以上是生活随笔為你收集整理的STL-hasmap源码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STL-bitset源码解析
- 下一篇: Cmake-2