【16期】你能談?wù)凥ashMap怎樣解決hash沖突嗎
閱讀本文大概需要 16 分鐘。
來自:iteye.com/blog/xiaolu123456-1485349
HashMap沖突解決方法比較考驗一個開發(fā)者解決問題的能力。 下文給出HashMap沖突的解決方法以及原理分析,無論是在面試問答或者實際使用中,應(yīng)該都會有所幫助。
HashMap<String,Object>?m=new?HashMap<String,Object>();?
m.put("a",?"rrr1");?
m.put("b",?"tt9");?
m.put("c",?"tt8");?
m.put("d",?"g7");?
m.put("e",?"d6");?
m.put("f",?"d4");?
m.put("g",?"d4");?
m.put("h",?"d3");?
m.put("i",?"d2");?
m.put("j",?"d1");?
m.put("k",?"1");?
m.put("o",?"2");?
m.put("p",?"3");?
m.put("q",?"4");?
m.put("r",?"5");?
m.put("s",?"6");?
m.put("t",?"7");?
m.put("u",?"8");?
m.put("v",?"9");
public?V?put(K?key,?V?value)?{??
????????if?(key?==?null)??
????????????return?putForNullKey(value);??
????????int?hash?=?hash(key.hashCode());??
????????int?i?=?indexFor(hash,?table.length);??
????????for?(Entry?e?=?table[i];?e?!=?null;?e?=?e.next)?{??
????????????Object?k;??
????????????//判斷當(dāng)前確定的索引位置是否存在相同hashcode和相同key的元素,如果存在相同的hashcode和相同的key的元素,那么新值覆蓋原來的舊值,并返回舊值。??
????????????//如果存在相同的hashcode,那么他們確定的索引位置就相同,這時判斷他們的key是否相同,如果不相同,這時就是產(chǎn)生了hash沖突。??
????????????//Hash沖突后,那么HashMap的單個bucket里存儲的不是一個 Entry,而是一個 Entry 鏈。??
????????????//系統(tǒng)只能必須按順序遍歷每個?Entry,直到找到想搜索的?Entry?為止——如果恰好要搜索的?Entry?位于該?Entry?鏈的最末端(該?Entry?是最早放入該?bucket?中),??
????????????//那系統(tǒng)必須循環(huán)到最后才能找到該元素。??
????????????if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{??
????????????????V?oldValue?=?e.value;??
????????????????e.value?=?value;??
????????????????return?oldValue;??
????????????}??
????????}??
????????modCount++;??
????????addEntry(hash,?key,?value,?i);??
????????return?null;??
????}??

鏈表法就是將相同hash值的對象組織成一個鏈表放在hash值對應(yīng)的槽位;
開放地址法是通過一個探測算法,當(dāng)某個槽位已經(jīng)被占據(jù)的情況下繼續(xù)查找下一個可以使用的槽位。
void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{??
????Entry?e?=?table[bucketIndex];??
????table[bucketIndex]?=?new?Entry(hash,?key,?value,?e);??
????if?(size++?>=?threshold)??
????????resize(2?*?table.length);??
bsp;??
一、HashMap概述
值得注意的是HashMap不是線程安全的,如果想要線程安全的HashMap,可以通過Collections類的靜態(tài)方法synchronizedMap獲得線程安全的HashMap。
?Map?map?=?Collections.synchronizedMap(new?HashMap());
二、HashMap的數(shù)據(jù)結(jié)構(gòu)

/** Entry是單向鏈表。????
?????*?它是?“HashMap鏈?zhǔn)酱鎯Ψā睂?yīng)的鏈表。????
?????*它實現(xiàn)了Map.Entry?接口,即實現(xiàn)getKey(),?getValue(),?setValue(V?value),?equals(Object?o),?hashCode()這些函數(shù)??
????**/??
????static?class?Entry<K,V>?implements?Map.Entry<K,V>?{????
????????final?K?key;????
????????V?value;????
????????//?指向下一個節(jié)點????
????????Entry?next;????
????????final?int?hash;????
????????//?構(gòu)造函數(shù)。????
????????//?輸入?yún)?shù)包括"哈希值(h)",?"鍵(k)",?"值(v)",?"下一節(jié)點(n)"????
????????Entry(int?h,?K?k,?V?v,?Entry?n)?{????
????????????value?=?v;????
????????????next?=?n;????
????????????key?=?k;????
????????????hash?=?h;????
????????}????
????????public?final?K?getKey()?{????
????????????return?key;????
????????}????
????????public?final?V?getValue()?{????
????????????return?value;????
????????}????
????????public?final?V?setValue(V?newValue)?{????
????????????V?oldValue?=?value;????
????????????value?=?newValue;????
????????????return?oldValue;????
????????}????
????????//?判斷兩個Entry是否相等????
????????//?若兩個Entry的“key”和“value”都相等,則返回true。????
????????//?否則,返回false????
????????public?final?boolean?equals(Object?o)?{????
????????????if?(!(o?instanceof?Map.Entry))????
????????????????return?false;????
????????????Map.Entry?e?=?(Map.Entry)o;????
????????????Object?k1?=?getKey();????
????????????Object?k2?=?e.getKey();????
????????????if?(k1?==?k2?||?(k1?!=?null?&&?k1.equals(k2)))?{????
????????????????Object?v1?=?getValue();????
????????????????Object?v2?=?e.getValue();????
????????????????if?(v1?==?v2?||?(v1?!=?null?&&?v1.equals(v2)))????
????????????????????return?true;????
????????????}????
????????????return?false;????
????????}????
????????//?實現(xiàn)hashCode()????
????????public?final?int?hashCode()?{????
????????????return?(key==null?????0?:?key.hashCode())?^????
???????????????????(value==null???0?:?value.hashCode());????
????????}????
????????public?final?String?toString()?{????
????????????return?getKey()?+?"="?+?getValue();????
????????}????
????????//?當(dāng)向HashMap中添加元素時,繪調(diào)用recordAccess()。????
????????//?這里不做任何處理????
????????void?recordAccess(HashMap?m) ?{????
????????}????
????????//?當(dāng)從HashMap中刪除元素時,繪調(diào)用recordRemoval()。????
????????//?這里不做任何處理????
????????void?recordRemoval(HashMap?m) ?{????
????????}????
????}
三、HashMap源碼分析
1、關(guān)鍵屬性
transient?Entry[]?table;//存儲元素的實體數(shù)組
transient?int?size;//存放元素的個數(shù)
int?threshold;?//臨界值???當(dāng)實際大小超過臨界值時,會進(jìn)行擴容threshold?=?加載因子*容量
?final?float?loadFactor;?//加載因子
transient?int?modCount;//被修改的次數(shù)
2、構(gòu)造方法
public?HashMap(int?initialCapacity,?float?loadFactor)?{
????????//確保數(shù)字合法
????????if?(initialCapacity?0)
????????????throw?new?IllegalArgumentException("Illegal?initial?capacity:?"?+
??????????????????????????????????????????????initialCapacity);
????????if?(initialCapacity?>?MAXIMUM_CAPACITY)
????????????initialCapacity?=?MAXIMUM_CAPACITY;
????????if?(loadFactor?<=?0?||?Float.isNaN(loadFactor))
????????????throw?new?IllegalArgumentException("Illegal?load?factor:?"?+
??????????????????????????????????????????????loadFactor);
????????//?Find?a?power?of?2?>=?initialCapacity
????????int?capacity?=?1;???//初始容量
????????while?(capacity?//確保容量為2的n次冪,使capacity為大于initialCapacity的最小的2的n次冪
????????????capacity?<<=?1;
????????this.loadFactor?=?loadFactor;
????????threshold?=?(int)(capacity?*?loadFactor);
????????table?=?new?Entry[capacity];
???????init();
???}
????public?HashMap(int?initialCapacity)?{
????????this(initialCapacity,?DEFAULT_LOAD_FACTOR);
???}
????public?HashMap()?{
????????this.loadFactor?=?DEFAULT_LOAD_FACTOR;
????????threshold?=?(int)(DEFAULT_INITIAL_CAPACITY?*?DEFAULT_LOAD_FACTOR);
????????table?=?new?Entry[DEFAULT_INITIAL_CAPACITY];
???????init();
????}
3、存儲數(shù)據(jù)
public?V?put(K?key,?V?value)?{
?????//?若“key為null”,則將該鍵值對添加到table[0]中。
?????????if?(key?==?null)?
????????????return?putForNullKey(value);
?????//?若“key不為null”,則計算該key的哈希值,然后將其添加到該哈希值對應(yīng)的鏈表中。
?????????int?hash?=?hash(key.hashCode());
?????//搜索指定hash值在對應(yīng)table中的索引
?????????int?i?=?indexFor(hash,?table.length);
?????//?循環(huán)遍歷Entry數(shù)組,若“該key”對應(yīng)的鍵值對已經(jīng)存在,則用新的value取代舊的value。然后退出!
?????????for?(Entry?e?=?table[i];?e?!=?null;?e?=?e.next)?{?
?????????????Object?k;
??????????????if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))?{?//如果key相同則覆蓋并返回舊值
??????????????????V?oldValue?=?e.value;
?????????????????e.value?=?value;
?????????????????e.recordAccess(this);
?????????????????return?oldValue;
??????????????}
?????????}
?????//修改次數(shù)+1
?????????modCount++;
?????//將key-value添加到table[i]處
?????addEntry(hash,?key,?value,?i);
?????return?null;
}
private?V?putForNullKey(V?value)?{
????????for?(Entry?e?=?table[0];?e?!=?null;?e?=?e.next)?{
????????????if?(e.key?==?null)?{???//如果有key為null的對象存在,則覆蓋掉
????????????????V?oldValue?=?e.value;
????????????????e.value?=?value;
????????????????e.recordAccess(this);
????????????????return?oldValue;
???????????}
???????}
????????modCount++;
????????addEntry(0,?null,?value,?0);?//如果鍵為null的話,則hash值為0
????????return?null;
????}
//計算hash值的方法?通過鍵的hashCode來計算
????static?int?hash(int?h)?{
????????//?This?function?ensures?that?hashCodes?that?differ?only?by
????????//?constant?multiples?at?each?bit?position?have?a?bounded
????????//?number?of?collisions?(approximately?8?at?default?load?factor).
????????h?^=?(h?>>>?20)?^?(h?>>>?12);
????????return?h?^?(h?>>>?7)?^?(h?>>>?4);
????}
static?int?indexFor(int?h,?int?length)?{?//根據(jù)hash值和數(shù)組長度算出索引值
?????????return?h?&?(length-1);??//這里不能隨便算取,用hash&(length-1)是有原因的,這樣可以確保算出來的索引是在數(shù)組大小范圍內(nèi),不會超出
}
???????h?&?(table.length-1)?????????????????????hash?????????????????????????????table.length-1
???????8?&?(15-1):?????????????????????????????????0100???????????????????&??????????????1110???????????????????=????????????????0100
???????9?&?(15-1):?????????????????????????????????0101???????????????????&??????????????1110???????????????????=????????????????0100
???????-----------------------------------------------------------------------------------------------------------------------
???????8?&?(16-1):?????????????????????????????????0100???????????????????&??????????????1111???????????????????=????????????????0100
???????9?&?(16-1):?????????????????????????????????0101???????????????????&??????????????1111???????????????????=????????????????0101
void?addEntry(int?hash,?K?key,?V?value,?int?bucketIndex)?{
????????Entry?e?=?table[bucketIndex];?//如果要加入的位置有值,將該位置原先的值設(shè)置為新entry的next,也就是新entry鏈表的下一個節(jié)點
???????table[bucketIndex]?=?new?Entry<>(hash,?key,?value,?e);
????????if?(size++?>=?threshold)?//如果大于臨界值就擴容
????????????resize(2?*?table.length);?//以2的倍數(shù)擴容
????}
4、調(diào)整大小
void?resize(int?newCapacity)?{
????????Entry[]?oldTable?=?table;
????????int?oldCapacity?=?oldTable.length;
????????if?(oldCapacity?==?MAXIMUM_CAPACITY)?{
????????????threshold?=?Integer.MAX_VALUE;
????????????return;
???????}
????????Entry[]?newTable?=?new?Entry[newCapacity];
????????transfer(newTable);//用來將原先table的元素全部移到newTable里面
????????table?=?newTable;??//再將newTable賦值給table
????????threshold?=?(int)(newCapacity?*?loadFactor);//重新計算臨界值
????}
5、數(shù)據(jù)讀取
public?V?get(Object?key)?{???
????if?(key?==?null)???
????????return?getForNullKey();???
????int?hash?=?hash(key.hashCode());???
????for?(Entry?e?=?table[indexFor(hash,?table.length)];???
????????e?!=?null;???
????????e?=?e.next)?{???
????????Object?k;???
????????if?(e.hash?==?hash?&&?((k?=?e.key)?==?key?||?key.equals(k)))???
????????????return?e.value;???
????}???
????return?null;???
}
6、HashMap的性能參數(shù):
HashMap():構(gòu)建一個初始容量為 16,負(fù)載因子為 0.75 的 HashMap。
HashMap(int initialCapacity):構(gòu)建一個初始容量為 initialCapacity,負(fù)載因子為 0.75 的 HashMap。
HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的負(fù)載因子創(chuàng)建一個 HashMap。
HashMap的基礎(chǔ)構(gòu)造器HashMap(int initialCapacity, float loadFactor)帶有兩個參數(shù),它們是初始容量initialCapacity和加載因子loadFactor。initialCapacity:HashMap的最大容量,即為底層數(shù)組的長度。
loadFactor:負(fù)載因子loadFactor定義為:散列表的實際元素數(shù)目(n)/ 散列表的容量(m)。
threshold?=?(int)(capacity?*?loadFactor);??
推薦閱讀:
微信掃描二維碼,關(guān)注我的公眾號
朕已閱?
評論
圖片
表情

