HashMap、HashSet、Hashtable的區別

忽然發現整理了不少筆記,應該放這裏作備用java

Hashtable和HashMap

主要區別:線程安全性,同步(synchronization),以及速度。算法

HashMap幾乎能夠等價於Hashtable,除了HashMap是非synchronized的,並能夠接受null。Hashtable是線程安全的,多個線程能夠共享一個Hashtable。數組

HashMap的同步問題可經過Collections的一個靜態方法獲得解決,Map Collections.synchronizedMap(Map m) 返回一個同步的Map。安全

HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。fail-fast結構上更改時(刪除或者插入一個元素),將會拋出ConcurrentModificationException異常。數據結構

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

HashSet和HashMap的區別

HashSet實現了Set接口,它不容許集合中有重複的值,當咱們提到HashSet時,第一件事情就是在將對象存儲在HashSet以前,要先確保對象重寫equals()和hashCode()方法,這樣才能比較對象的值是否相等,以確保set中沒有儲存相等的對象。若是咱們沒有重寫這兩個方法,將會使用這個方法的默認實現。併發

Map中不容許重複的鍵。Map接口有兩個基本的實現,HashMap和TreeMap。TreeMap保存了對象的排列次序,而HashMap則不能。HashMap容許鍵和值爲null。性能

HashSet 和 HashMap 之間有不少類似之處,對於 HashSet 而言,系統採用 Hash 算法決定集合元素的存儲位置,這樣能夠保證能快速存、取集合元素;對於 HashMap 而言,系統 key-value 當成一個總體進行處理,系統老是根據 Hash 算法來計算 key-value 的存儲位置,這樣能夠保證能快速存、取 Map 的 key-value 對。優化

HashMap

public V put(K key, V value) {   
    // 若是 key 爲 null,調用 putForNullKey 方法進行處理  
    if (key == null)   
        return putForNullKey(value);   
    // 根據 key 的 keyCode 計算 Hash 值  
    int hash = hash(key.hashCode());   
    // 搜索指定 hash 值在對應 table 中的索引  
    int i = indexFor(hash, table.length);  
    // 若是 i 索引處的 Entry 不爲 null,經過循環不斷遍歷 e 元素的下一個元素  
    for (Entry<K,V> e = table[i]; e != null; e = e.next)   
    {   
        Object k;   
        // 找到指定 key 與須要放入的 key 相等(hash 值相同  
        // 經過 equals 比較放回 true)  
        if (e.hash == hash && ((k = e.key) == key   
            || key.equals(k)))   
        {   
            V oldValue = e.value;   
            e.value = value;   
            e.recordAccess(this);   
            return oldValue;   
        }   
    }   
    // 若是 i 索引處的 Entry 爲 null,代表此處尚未 Entry   
    modCount++;   
    // 將 key、value 添加到 i 索引處  
    addEntry(hash, key, value, i);   
    return null;   
}

Map.Entry,每一個 Map.Entry 其實就是一個 key-value 對。this

當系統決定存儲 HashMap 中的 key-value 對時,徹底沒有考慮 Entry 中的 value,僅僅只是根據 key 來計算並決定每一個 Entry 的存儲位置。能夠把 Map 集合中的 value 當成 key 的附屬。

indexFor(int h, int length) 方法來計算該對象應該保存在 table 數組的哪一個索引處。

根據上面 put 方法的源代碼能夠看出,當程序試圖將一個 key-value 對放入 HashMap 中時,程序首先根據該 key 的 hashCode() 返回值決定該 Entry 的存儲位置:若是兩個 Entry 的 key 的 hashCode() 返回值相同,那它們的存儲位置相同。若是這兩個 Entry 的 key 經過 equals 比較返回 true,新添加 Entry 的 value 將覆蓋集合中原有 Entry 的 value,但 key 不會覆蓋。若是這兩個 Entry 的 key 經過 equals 比較返回 false,新添加的 Entry 將與集合中原有 Entry 造成 Entry 鏈,並且新添加的 Entry 位於 Entry 鏈的頭部。

Map提供了一些經常使用方法,如keySet()、entrySet()等方法,keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一個Set集合,此集合的類型爲Map.Entry,接口中有getKey()、getValue方法。

內部實現

HashMap其實是一個「鏈表散列」的數據結構,即數組和鏈表的結構,可是在jdk1.8裏 ,加入了紅黑樹的實現,當鏈表的長度大於8時,轉換爲紅黑樹的結構。

少於8個的時候,Java中HashMap採用了鏈地址法。

經過什麼方式來控制map使得Hash碰撞的機率又小,哈希桶數組(Node[] table)佔用空間又少呢?答案就是好的Hash算法和擴容機制。即便負載因子和Hash算法設計的再合理,也免不了會出現拉鍊過長的狀況,一旦出現拉鍊過長,則會嚴重影響HashMap的性能。

而當鏈表長度太長(默認超過8)時,鏈表就轉換爲紅黑樹,利用紅黑樹快速增刪改查的特色提升HashMap的性能。

indexFor方法通常想到的是把hash值對數組長度取模運算,但運算較大,所以1.7中使用如下方法,&比%具備更高的效率:

static int indexFor(int h, int length) { 
     return h & (length-1);  //第三步 取模運算
}

在JDK1.8的實現中,優化了高位運算的算法:(h = k.hashCode()) ^ (h >>> 16)

紅黑樹

紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹。紅黑樹和AVL樹同樣都對插入時間、刪除時間和查找時間提供了最好可能的最壞狀況擔保。除了O(log n)的時間以外,紅黑樹的持久版本對每次插入或刪除須要O(log n)的空間。

equals()和hashCode()

兩個obj,若是equals()相等,hashCode()必定相等。
兩個obj,若是hashCode()相等,equals()不必定相等(Hash散列值有衝突的狀況,雖然機率很低)。

equals()和hashcode()這兩個方法都是從object類中繼承過來的。equals()是對兩個對象的地址值進行的比較(即比較引用是否相同),hashCode()是一個本地方法,它的實現是根據本地機器相關的。

hashCode()在new的用途

JVM每new一個Object,它都會將這個Object丟到一個Hash哈希表中去,這樣的話,下次作Object的比較或者取這個對象的時候,它會根據對象的hashcode再從Hash表中取這個對象。這樣作的目的是提升取對象的效率。

若是不一樣的對象確產生了相同的hash值,也就是發生了Hash key相同致使衝突的狀況,那麼就在這個Hash key的地方產生一個鏈表,將全部產生相同hashcode的對象放到這個單鏈表上去,串在一塊兒。比較兩個對象的時候,首先根據他們的hashcode去hash表中找他的對象,當兩個對象的hashcode相同,只能根據Object的equal方法來比較這個對象是否equal。

重寫equals()的緣由

Object的equal方法默認是兩個對象的引用的比較,意思就是指向同一內存。

可是,String對象中equals方法是判斷值的,而==是地址判斷(由於JDK重寫了)。

咱們很大部分時間都是進行兩個對象的比較(而不是比較引用),這個時候Object的equals()方法就不能夠了,因此纔會有String這些類對equals方法的改寫,依次類推Double、Integer、Math等等這些類都是重寫了equals()方法的,從而進行的是內容的比較。

重寫equals()後要重寫hashCode()的理由

java.lnag.Object中對hashCode的約定:

  1. 在一個應用程序執行期間,若是一個對象的equals方法作比較所用到的信息沒有被修改的話,則對該對象調用hashCode方法屢次,它必須始終如一地返回同一個整數。
  2. 若是兩個對象根據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生相同的整數結果。
  3. 若是兩個對象根據equals(Object o)方法是不相等的,則調用這兩個對象中任一個對象的hashCode方法,不要求產生不一樣的整數結果。但若是能不一樣,則可能提升散列表的性能。

只要改寫了就會違約,因此要繼續改寫。

Hashtable與ConcurrentHashMap區別

ConcurrentHashMap融合了hashtable和hashmap兩者的優點。hashmap在單線程狀況下效率較高;hashtable在的多線程狀況下,同步操做能保證程序執行的正確性。

hashtable每次同步執行的時候都要鎖住整個結構:

ConcurrentHashMap鎖的方式是稍微細粒度的。

更使人驚訝的是ConcurrentHashMap的讀取併發,由於在讀取的大多數時候都沒有用到鎖定,因此讀取操做幾乎是徹底的併發操做,而寫操做鎖定的粒度又很是細,比起以前又更加快速(這一點在桶更多時表現得更明顯些)。只有在求size等操做時才須要鎖定整個表。

在迭代時,使用弱一致迭代器,再也不是拋出 ConcurrentModificationException,在改變時new新的數據從而不影響原有的數據,iterator完成後再將頭指針替換爲新的數據。

常見問題

HashMap的工做原理,HashMap的get()方法的工做原理

答:「HashMap是基於hashing的原理,咱們使用put(key, value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。當咱們給put()方法傳遞鍵和值時,咱們先對鍵調用hashCode()方法,返回的hashCode用於找到bucket位置來儲存Entry對象。」

關鍵:HashMap是在bucket中儲存鍵對象和值對象,做爲Map.Entry。

當兩個對象的hashcode相同會發生什麼

由於hashcode相同,因此它們的bucket位置相同,發生衝突,Entry(包含有鍵值對的Map.Entry對象)會存儲在鏈表中。

hashcode相同如何獲取值對象

遍歷鏈表直到找到Entry對象,找到bucket位置以後,會調用keys.equals()方法去找到鏈表中正確的節點。

超過了負載因子(load factor)定義的容量

默認的負載因子大小爲0.75,將會建立原來HashMap大小的兩倍的bucket數組,來從新調整map的大小(rehashing)。當多線程的狀況下,可能產生條件競爭(race condition),兩個線程都發現HashMap須要從新調整大小了,它們會同時試着調整大小。

先這樣吧

如有錯誤之處請指出,更多地關注煎魚

相關文章
相關標籤/搜索