兩個物件HashCode相同即 雜湊碰撞解決方案
先了解下Java中堆疊的功能分配,
棧是執行時的單位 , 而堆是儲存的單元,棧解決程式的執行問題,即程式如何執行,或者說如何處理資料,堆解決的是資料儲存的問題,即資料怎麼放,放在哪兒。
我們知道,物件Hash的前提是實現equals()和hashCode()兩個方法,那麼HashCode()的作用就是保證物件返回唯一hash值,但當兩個物件計算值一樣時,這就發生了碰撞衝突。如下將介紹如何處理衝突,當然其前提是一致性hash。
原始碼分析
HashMap 採用一種所謂的“Hash 演算法”來決定每個元素的儲存位置。當程式執行 map.put(String,Obect)方法 時,系統將呼叫String的 hashCode() 方法得到其 hashCode 值——每個 Java 物件都有 hashCode() 方法,都可通過該方法獲得它的 hashCode 值。得到這個物件的 hashCode 值之後,系統會根據該 hashCode 值來決定該元素的儲存位置。原始碼如下:
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<K,V> e = table[i]; e != null; e = e.next) { Object k; //判斷當前確定的索引位置是否存在相同hashcode和相同key的元素,如果存在相同的hashcode和相同的key的元素,那麼新值覆蓋原來的舊值,並返回舊值。 //如果存在相同的hashcode,那麼他們確定的索引位置就相同,這時判斷他們的key是否相同,如果不相同,這時就是產生了hash衝突。 //Hash衝突後,那麼HashMap的單個bucket裡儲存的不是一個 Entry,而是一個 Entry 鏈。 //系統只能必須按順序遍歷每個 Entry,直到找到想搜尋的 Entry 為止——如果恰好要搜尋的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最早放入該 bucket 中), //那系統必須迴圈到最後才能找到該元素。 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; }
當系統決定儲存 HashMap 中的 key-value 對時,完全沒有考慮 Entry 中的 value,僅僅只是根據 key 來計算並決定每個 Entry 的儲存位置。這也說明了前面的結論:我們完全可以把 Map 集合中的 value 當成 key 的附屬,當系統決定了 key 的儲存位置之後,value 隨之儲存在那裡即可
連結串列法
Hashmap裡面的bucket出現了單鏈表的形式,散列表要解決的一個問題就是雜湊值的衝突問題,通常是兩種方法:連結串列法和開放地址法。連結串列法就是將相同hash值的物件組織成一個連結串列放在hash值對應的槽位;開放地址法是通過一個探測演算法,當某個槽位已經被佔據的情況下繼續查詢下一個可以使用的槽位。java.util.HashMap採用的連結串列法的方式,連結串列是單向連結串列。形成單鏈表的核心程式碼如下:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
上面方法的程式碼很簡單,但其中包含了一個設計:系統總是將新新增的 Entry 物件放入 table 陣列的 bucketIndex 索引處——如果 bucketIndex 索引處已經有了一個 Entry 物件,那新新增的 Entry 物件指向原有的 Entry 物件(產生一個 Entry 鏈),如果 bucketIndex 索引處沒有 Entry 物件,也就是上面程式程式碼的 e 變數是 null,也就是新放入的 Entry 物件指向 null,也就是沒有產生 Entry 鏈。
HashMap裡面沒有出現hash衝突時,沒有形成單鏈表時,hashmap查詢元素很快,get()方法能夠直接定位到元素,但是出現單鏈表後,單個bucket 裡儲存的不是一個 Entry,而是一個 Entry 鏈,系統只能必須按順序遍歷每個 Entry,直到找到想搜尋的 Entry 為止——如果恰好要搜尋的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最早放入該 bucket 中),那系統必須迴圈到最後才能找到該元素。
當建立 HashMap 時,有一個預設的負載因子(load factor),其預設值為 0.75,這是時間和空間成本上一種折衷:增大負載因子可以減少 Hash 表(就是那個 Entry 陣列)所佔用的記憶體空間,但會增加查詢資料的時間開銷,而查詢是最頻繁的的操作(HashMap 的 get() 與 put() 方法都要用到查詢);減小負載因子會提高資料查詢的效能,但會增加 Hash 表所佔用的記憶體空間。
其實我也不是特別懂,只是模糊的瞭解吧,後面再細談