HashMap碰撞問題

HashMap是最經常使用的集合類框架之一,它實現了Map接口,因此存儲的元素也是鍵值對映射的結構,並容許使用null值和null鍵,其內元素是無序的,若是要保證有序,可使用LinkedHashMap。HashMap是線程不安全的,下篇文章會討論。HashMap的類關係以下:java

    java.util 算法

    Class HashMap<K,V>安全

      java.lang.Objectapp

          |--java.util.AbstractMap<K,V>框架

                |--java.util.HashMap<K,V>this

全部已實現的接口:spa

  Serializable,Cloneable,Map<K,V>線程

直接已知子類:code

  LinkedHashMap,PrinterStateReasons對象

 

  HashMap中用的最多的方法就屬put() 和 get() 方法;HashMap的Key值是惟一的,那如何保證惟一性呢?咱們首先想到的是用equals比較,沒錯,這樣能夠實現,但隨着內部元素的增多,put和get的效率將愈來愈低,這裏的時間複雜度是O(n),假若有1000個元素,put時最差狀況須要比較1000次。實際上,HashMap不多會用到equals方法,由於其內經過一個哈希表管理全部元素,哈希是經過hash單詞音譯過來的,也能夠稱爲散列表,哈希算法能夠快速的存取元素,當咱們調用put存值時,HashMap首先會調用Key的hash方法,計算出哈希碼,經過哈希碼快速找到某個存放位置(桶),這個位置能夠被稱之爲bucketIndex,但可能會存在多個元素找到了相同的bucketIndex,有個專業名詞叫碰撞,當碰撞發生時,這時會取到bucketIndex位置已存儲的元素,最終經過equals來比較,equals方法就是碰撞時纔會執行的方法,因此前面說HashMap不多會用到equals。HashMap經過hashCode和equals最終判斷出Key是否已存在,若是已存在,則使用新Value值替換舊Value值,並返回舊Value值,若是不存在 ,則存放新的鍵值對<K, V>到bucketIndex位置。經過下面的流程圖來梳理一下整個put過程。

           

 

 

最終HashMap的存儲結構會有這三種狀況,咱們固然指望情形3是最少發生的(效率最低)。

 

HashMap 碰撞問題處理:

  碰撞:所謂「碰撞」就上面所述是多個元素計算得出相同的hashCode,在put時出現衝突。

  處理方法:

  Java中HashMap是利用「拉鍊法」處理HashCode的碰撞問題。在調用HashMap的put方法或get方法時,都會首先調用hashcode方法,去查找相關的key,當有衝突時,再調用equals方法。hashMap基於hasing原理,咱們經過put和get方法存取對象。當咱們將鍵值對傳遞給put方法時,他調用鍵對象的hashCode()方法來計算hashCode,而後找到bucket(哈希桶)位置來存儲對象。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。HashMap使用鏈表來解決碰撞問題,當碰撞發生了,對象將會存儲在鏈表的下一個節點中。hashMap在每一個鏈表節點存儲鍵值對對象。當兩個不一樣的鍵卻有相同的hashCode時,他們會存儲在同一個bucket位置的鏈表中。鍵對象的equals()來找到鍵值對。

 HashMap基本結構概念圖:

      

 

到目前爲止,咱們瞭解了兩件事:

  一、HashMap經過鍵的hashCode來快速的存取元素。

  二、當不一樣的對象發生碰撞時,HashMap經過單鏈表來解決,將新元素加入鏈表表頭,經過next指向原有的元素。單鏈表在Java中的實現就是對象的引用(複合)。

 

 HashMap.put()和get()源碼:

/** 
 * Returns the value to which the specified key is mapped, 
 * or if this map contains no mapping for the key. 
 * 
 * 獲取key對應的value 
 */  
public V get(Object key) {  
    if (key == null)  
        return getForNullKey();  
    //獲取key的hash值  
    int hash = hash(key.hashCode());  
    // 在「該hash值對應的鏈表」上查找「鍵值等於key」的元素  
    for (Entry<K,V> 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;  
}  

/** 
 * Offloaded version of get() to look up null keys.  Null keys map 
 * to index 0.   
 * 獲取key爲null的鍵值對,HashMap將此鍵值對存儲到table[0]的位置 
 */  
private V getForNullKey() {  
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
        if (e.key == null)  
            return e.value;  
    }  
    return null;  
}  

/** 
 * Returns <tt>true</tt> if this map contains a mapping for the 
 * specified key. 
 * 
 * HashMap是否包含key 
 */  
public boolean containsKey(Object key) {  
    return getEntry(key) != null;  
}  

/** 
 * Returns the entry associated with the specified key in the 
 * HashMap.   
 * 返回鍵爲key的鍵值對 
 */  
final Entry<K,V> getEntry(Object key) {  
    //先獲取哈希值。若是key爲null,hash = 0;這是由於key爲null的鍵值對存儲在table[0]的位置。  
    int hash = (key == null) ? 0 : hash(key.hashCode());  
    //在該哈希值對應的鏈表上查找鍵值與key相等的元素。  
    for (Entry<K,V> e = table[indexFor(hash, table.length)];  
         e != null;  
         e = e.next) {  
        Object k;  
        if (e.hash == hash &&  
            ((k = e.key) == key || (key != null && key.equals(k))))  
            return e;  
    }  
    return null;  
}  


/** 
 * Associates the specified value with the specified key in this map. 
 * If the map previously contained a mapping for the key, the old 
 * value is replaced. 
 * 
 * 將「key-value」添加到HashMap中,若是hashMap中包含了key,那麼原來的值將會被新值取代 
 */  
public V put(K key, V value) {  
    //若是key是null,那麼調用putForNullKey(),將該鍵值對添加到table[0]中  
    if (key == null)  
        return putForNullKey(value);  
    //若是key不爲null,則計算key的哈希值,而後將其添加到哈希值對應的鏈表中  
    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;  
        //若是這個key對應的鍵值對已經存在,就用新的value代替老的value。  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
            V oldValue = e.value;  
            e.value = value;  
            e.recordAccess(this);  
            return oldValue;  
        }  
    }  

    modCount++;  
    addEntry(hash, key, value, i);  
    return null;  
}

從HashMap的put()和get方法實現中能夠與拉鍊法解決hashCode衝突解決方法相互印證。而且從put方法中能夠看出HashMap是使用Entry<K,V>來存儲數據。

相關文章
相關標籤/搜索