Java HashMap的實現原理

昨天在羣裏討論的時候,有人突然說HashMap的實現做面試題考新人一考一個準,一下子就能區分開是不是真的有工作經驗.媽蛋,我竟然也不是很確認,特此總結一下:


對於HashMap來說,你的每一次put操作後,都會對key取一次hashcode放入table中.table的每一個位置是一個HashMapEntry,因爲key的hashcode有可能相同,這時table同一個位置的HashMapEntry就會next中追加一個HashMapEntry.

@Override public V put(K key, V value) {   if (key == null) {        
  return putValueForNullKey(value);    
}  
int hash = secondaryHash(key);    
HashMapEntry<K, V>[] tab = table;  
int index = hash & (tab.length - 1);
for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {        
if (e.hash == hash && key.equals(e.key)) {            
   preModify(e);            
  V oldValue = e.value;            
  e.value = value;            
  return oldValue;        
}    
}    
// No entry for (non-null) key is present; create one    
modCount++;    
if (size++ > threshold) {        
  tab = doubleCapacity();        
  index = hash & (tab.length - 1);    
}    
addNewEntry(key, value, hash, index);  
  return null;
}
void addNewEntry(K key, V value, int hash, int index) {        
  table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}

計算 Hash 碼的方法:hash(),這個方法是一個純粹的數學計算,其方法如下:

...
int hash = key.hashCode();
hash ^= (hash >>> 20) ^ (hash >>> 12);
hash ^= (hash >>> 7) ^ (hash >>> 4);
...

存值或者取值的時候,也是通過key的hash的方式去取,它總是通過h &(table.length -1)來得到該對象的保存位置——而 HashMap 底層數組的長度總是2的n次方,這一點可參看後面關於HashMap構造器的介紹。當length總是2的倍數時,h & (length-1)將是一個非常巧妙的設計:假設h=5,length=16, 那麼h & length - 1將得到5;如果h=6,length=16, 那麼h & length - 1將得到6……如果h=15,length=16, 那麼h & length - 1將得到 15;但是當h=16時,length=16時,那麼h & length - 1將得到0了;當h=17時,length=16時,那麼h & length - 1將得到 1 了……這樣保證計算得到的索引值總是位於 table 數組的索引之內。

640?tp=webp

static int indexFor(int h, int length) {        
//android 的實現是直接使用h & (length-1),無此方法    
   return  h & (length-1);
}

當hashmap中的元素越來越多的時候,碰撞的機率也就越來越高(因爲數組的長度是固定的),所以爲了提高查詢的效率,就要對hashmap的數組進行擴容,數組擴容這個操作也會出現在ArrayList中,所以這是一個通用的操作,很多人對它的性能表示過懷疑,不過想想我們的“均攤”原理,就釋然了,而在hashmap數組擴容之後,最消耗性能的點就出現了:原數組中的數據必須重新計算其在新數組中的位置,並放進去,這就是resize。那麼hashmap什麼時候進行擴容呢?當hashmap中的元素個數超過數組大小*loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,也就是說,默認情況下,數組大小爲16,那麼當hashmap中元素個數超過16*0.75=12的時候,就把數組的大小擴展爲2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置,而這是一個非常消耗性能的操作,所以如果我們已經預知hashmap中元素的個數,那麼預設元素的個數能夠有效的提高hashmap的性能。比如說,我們有1000個元素new HashMap(1000), 但是理論上來講new HashMap(1024)更合適,不過上面angeyu已經說過,即使是1000,hashmap也自動會將其設置爲1024。 但是new HashMap(1024)還不是更合適的,因爲0.75*1000 < 1000, 也就是說爲了讓0.75 * size > 1000, 我們必須這樣new HashMap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章