class HashMap<K,V> extends AbstractMap<K,V>html
1.put()算法
HashMap put()方法源碼以下:數組
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; }
hash值衝突是發生在put()時,從源碼能夠看出,hash值是經過hash(key.hashCode())來獲取的,當put的元素愈來愈多時,不免或出現不一樣的key產生相同的hash值問題,也便是hash衝突,當拿到一個hash值,經過indexFor(hash, table.length)獲取數組下標,先查詢是否存在該hash值,若不存在,則直接以Entry<V,V>的方式存放在數組中,若存在,則再對比key是否相同,若hash值和key都相同,則替換value,若hash值相同,key不相同,則造成一個單鏈表,將hash值相同,key不一樣的元素以Entry<V,V>的方式存放在鏈表中,這樣就解決了hash衝突,這種方法叫作分離鏈表法,與之相似的方法還有一種叫作 開放定址法,開放定址法師採用線性探測(從相同hash值開始,繼續尋找下一個可用的槽位)hashMap是數組,長度雖然能夠擴大,但用線性探測法去查詢槽位查不到時怎麼辦?所以hashMap採用了分離鏈表法。性能
2.get()spa
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); 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; }
有了上面存儲時的hash算法做爲基礎,理解起來這段代碼就很容易了。從上面的源代碼中能夠看出:從HashMap中get元素時,首先計算key的hashCode,找到數組中對應位置的某一元素,而後經過key的equals方法在對應位置的鏈表中找到須要的元素。code
當hashMap沒出現hash衝突時,沒有造成單向鏈表,get方法可以直接定位到元素,可是,出現衝突後,造成了單向鏈表,bucket裏存放的再也不是一個entry對象,而是一個entry對象鏈,系統只能順序的遍歷每一個entry直到找到想要搜索的entry爲止,這時,問題就來了,若是剛好要搜索的entry位於該entry鏈的最末端,那循環必需要進行到最後一步才能找到元素,此時涉及到一個負載因子的概念,hashMap默認的負載因子爲0.75,這是考慮到存儲空間和查詢時間上成本的一個折中值,增大負載因子,能夠減小hash表(就是那個entry數組)所佔用的內空間,但會增長查詢數據的時間開銷,而查詢是最頻繁的操做(put()和get()都用到查詢);減少負載因子,會提升查詢時間,但會增長hash表所佔的內存空間。htm
結合負載因子的定義公式可知,threshold就是在此loadFactor和capacity對應下容許的最大元素數目,超過這個數目就從新resize,以下降實際的負載因子。默認的的負載因子0.75是對空間和時間效率的一個平衡選擇。當容量超出此最大容量時, resize後的HashMap容量是容量的兩倍:對象
3.hashMap數組擴容blog
當HashMap中的元素愈來愈多的時候,hash衝突的概率也就愈來愈高,由於數組的長度是固定的。因此爲了提升查詢的效率,就要對HashMap的數組進行擴容,數組擴容這個操做也會出如今ArrayList中,這是一個經常使用的操做,而在HashMap數組擴容以後,最消耗性能的點就出現了:原數組中的數據必須從新計算其在新數組中的位置,並放進去,這就是resize。索引
那麼HashMap何時進行擴容呢?當HashMap中的元素個數超過數組大小*loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,這是一個折中的取值。也就是說,默認狀況下,數組大小爲16,那麼當HashMap中元素個數超過16*0.75=12的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,而後從新計算每一個元素在數組中的位置,擴容是須要進行數組複製的,複製數組是很是消耗性能的操做,因此若是咱們已經預知HashMap中元素的個數,那麼預設元素的個數可以有效的提升HashMap的性能。
有關負載因子的概念 參考:https://www.cnblogs.com/yesiamhere/p/6653135.html