面試總結hashmap

鏈表結構:html

static class HashMapEntry<K, V> implements Entry<K, V> {java

        final K key;面試

        V value;算法

        final int hash;數組

        HashMapEntry<K, V> next;緩存

        ......安全

}多線程

 

數組存儲全部鏈表:app

transient HashMapEntry<K, V>[] table;   //後面tab=table性能

 

key的hash值的計算:

int hash = Collections.secondaryHash(key);

 

table中HashMapEntry位置的計算:

//經過key的hash值得到,由於HashMap數組的大小老是2^n,因此實際的運算就是 (0xfff...ff) & hash ,這裏的tab.length-1至關於一個mask,濾掉了大於當前長度位的hash,使每一個i都能插入到數組中。

int index = hash & (tab.length - 1); 

 

新增元素:

//放在鏈表的最前面,next = table[index]

table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);

 

取元素:

//找到key的hash對應的HashMapEntry,而後遍歷鏈表,經過key.equals取值

for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) {

      K eKey = e.key;

      if (eKey == key || (e.hash == hash && key.equals(eKey))) {

           return e.value;

      }

}

 

 

常見問題:

 (其實瞭解上面的基本知識,下面的不少問題都好理解了)

 

當兩個不一樣的鍵對象的hashcode相同時會發生什麼? 

它們會儲存在同一個bucket位置的HashMapEntry組成的鏈表中。

 

 

若是兩個鍵的hashcode相同,你如何獲取值對象?

當咱們調用get()方法,HashMap會使用鍵對象的hashcode找到bucket位置,找到bucket位置以後,會調用keys.equals()方法去找到鏈表中正確的節點。

  

什麼是hash,什麼是碰撞,什麼是equals ?

Hash:是一種信息摘要算法,它還叫作哈希,或者散列。咱們平時使用的MD5,SHA1都屬於Hash算法,經過輸入key進行Hash計算,就能夠獲取key的HashCode(),好比咱們經過校驗MD5來驗證文件的完整性。

對於HashCode,它是一個本地方法,實質就是地址取樣運算

 

碰撞:好的Hash算法能夠出計算幾乎出獨一無二的HashCode,若是出現了重複的hashCode,就稱做碰撞;

就算是MD5這樣優秀的算法也會發生碰撞,即兩個不一樣的key也有可能生成相同的MD5。

 

HashCode,它是一個本地方法,實質就是地址取樣運算;

==是用於比較指針是否在同一個地址;

equals與==是相同的。

  

如何減小碰撞?

使用不可變的、聲明做final的對象,而且採用合適的equals()和hashCode()方法的話,將會減小碰撞的發生,提升效率。不可變性使得可以緩存不一樣鍵的hashcode,這將提升整個獲取對象的速度,使用String,Interger這樣的wrapper類做爲鍵是很是好的選擇

 

若是HashMap的大小超過了負載因子(load factor)定義的容量,怎麼辦?

默認的負載因子大小爲0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)同樣,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫做rehashing,由於它調用hash方法找到新的bucket位置。

從新調整HashMap大小存在什麼問題嗎?

(當多線程的狀況下,可能產生條件競爭(race condition))

當從新調整HashMap大小的時候,確實存在條件競爭,由於若是兩個線程都發現HashMap須要從新調整大小了,它們會同時試着調整大小。在調整大小的過程當中,存儲在鏈表中的元素的次序會反過來,由於移動到新的bucket位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是爲了不尾部遍歷(tail traversing)。若是條件競爭發生了,那麼就死循環了。這個時候,你能夠質問面試官,爲何這麼奇怪,要在多線程的環境下使用HashMap呢?

 

爲何String, Interger這樣的wrapper類適合做爲鍵?

由於String是不可變的,也是final的,並且已經重寫了equals()和hashCode()方法了。其餘的wrapper類也有這個特色。不可變性是必要的,由於爲了要計算hashCode(),就要防止鍵值改變,若是鍵值在放入時和獲取時返回不一樣的hashcode的話,那麼就不能從HashMap中找到你想要的對象。不可變性還有其餘的優勢如線程安全。若是你能夠僅僅經過將某個field聲明成final就能保證hashCode是不變的,那麼請這麼作吧。由於獲取對象的時候要用到equals()和hashCode()方法,那麼鍵對象正確的重寫這兩個方法是很是重要的。若是兩個不相等的對象返回不一樣的hashcode的話,那麼碰撞的概率就會小些,這樣就能提升HashMap的性能。

  

可使用自定義的對象做爲鍵嗎?

固然你可能使用任何對象做爲鍵,只要它遵照了equals()和hashCode()方法的定義規則,而且當對象插入到Map中以後將不會再改變了。若是這個自定義對象時不可變的,那麼它已經知足了做爲鍵的條件,由於當它建立以後就已經不能改變了。

 

可使用CocurrentHashMap來代替Hashtable嗎?

Hashtable是synchronized的,可是ConcurrentHashMap同步性能更好,由於它僅僅根據同步級別對map的一部分進行上鎖。ConcurrentHashMap固然能夠代替HashTable,可是HashTable提供更強的線程安全性。

 

可否讓HashMap同步?

HashMap能夠經過下面的語句進行同步:

Map m = Collections.synchronizeMap(hashMap);

 

HashMap和Hashtable的區別:

主要的不一樣:線程安全以及速度。僅在你須要徹底的線程安全的時候使用Hashtable,而若是你使用Java 5或以上的話,請使用ConcurrentHashMap吧。

 HashMap和Hashtable都實現了Map接口,但決定用哪個以前先要弄清楚它們之間的分別。主要的區別有:線程安全性,同步(synchronization),以及速度。

 HashMap幾乎能夠等價於Hashtable,除了HashMap是非synchronized的,並能夠接受null(HashMap能夠接受爲null的鍵值(key)和值(value),而Hashtable則不行)。

HashMap是非synchronized,而Hashtable是synchronized,這意味着Hashtable是線程安全的,多個線程能夠共享一個Hashtable;而若是沒有正確的同步的話,多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。

另外一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此當有其它線程改變了HashMap的結構(增長或者移除元素),將會拋出ConcurrentModificationException,但迭代器自己的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並非一個必定發生的行爲,要看JVM。這條一樣也是Enumeration和Iterator的區別。

因爲Hashtable是線程安全的也是synchronized,因此在單線程環境下它比HashMap要慢。若是你不須要同步,只須要單一線程,那麼使用HashMap性能要好過Hashtable。

HashMap不能保證隨着時間的推移Map中的元素次序是不變的。

要注意的一些重要術語:

1) sychronized意味着在一次僅有一個線程可以更改Hashtable。就是說任何線程要更新Hashtable時要首先得到同步鎖,其它線程要等到同步鎖被釋放以後才能再次得到同步鎖更新Hashtable。

2) Fail-safe和iterator迭代器相關。若是某個集合對象建立了Iterator或者ListIterator,而後其它的線程試圖「結構上」更改集合對象,將會拋出ConcurrentModificationException異常。但其它線程能夠經過set()方法更改集合對象是容許的,由於這並無從「結構上」更改集合。可是假如已經從結構上進行了更改,再調用set()方法,將會拋出IllegalArgumentException異常。

3) 結構上的更改指的是刪除或者插入一個元素,這樣會影響到map的結構。

相關文章
相關標籤/搜索