一、HashMap、TreeMap都繼承AbstractMap抽象類;TreeMap實現SortedMap接口,因此TreeMap是有序的!HashMap是無序的。
接口層次:
public interface SortedMap<K,V> extends Map<K,V>
public interface NavigableMap<K,V> extends SortedMap<K,V>
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializablejava
二、兩種常規Map性能
HashMap:適用於在Map中插入、刪除和定位元素。
Treemap:適用於按天然順序或自定義順序遍歷鍵(key)。算法
使用場景:HashMap一般比TreeMap快一點(樹和哈希表的數據結構使然),建議多使用HashMap,在須要排序的Map時候才用TreeMap。數組
三、HashMap和Hashtable的區別安全
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中的元素次序是不變的。數據結構
咱們可否讓HashMap同步?
HashMap能夠經過下面的語句進行同步:
Map m = Collections.synchronizeMap(hashMap);多線程
HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的狀況下HashTable的效率很是低下。由於當一個線程訪問HashTable的同步方法時,其餘線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,而且也不能使用get方法來獲取元素,因此競爭越激烈效率越低。併發
HashTable容器在競爭激烈的併發環境下表現出效率低下的緣由,是由於全部訪問HashTable的線程都必須競爭同一把鎖,那假如容器裏有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裏不一樣數據段的數據時,線程間就不會存在鎖競爭,從而能夠有效的提升併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分紅一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問。app
四、ConcurrentMap 高併發
ConcurrentHashMap 表現區別:不能夠有null鍵,線程安全,原子操做。一個ConcurrentHashMap 由多個segment 組成,每一個segment 包含一個Entity 的數組。這裏比HashMap 多了一個segment 類。該類繼承了ReentrantLock 類,因此自己是一個鎖。當多線程對ConcurrentHashMap 操做時,不是徹底鎖住map, 而是鎖住相應的segment 。這樣提升了併發效率。缺點:當遍歷ConcurrentMap中的元素時,須要獲取全部的segment 的鎖,使用遍歷時慢。鎖的增多,佔用了系統的資源。使得對整個集合進行操做的一些方法性能
Segment的get操做實現很是簡單和高效。先通過一次再哈希,而後使用這個哈希值經過哈希運算定位到segment,再經過哈希算法定位到元素,代碼以下:
public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash); }
get操做的高效之處在於整個get過程不須要加鎖,除非讀到的值是空的纔會加鎖重讀,咱們知道HashTable容器的get方法是須要加鎖的,那麼ConcurrentHashMap的get操做是如何作到不加鎖的呢?緣由是它的get方法裏將要使用的共享變量都定義成volatile,如用於統計當前Segement大小的count字段和用於存儲值的HashEntry的value。定義成volatile的變量,可以在線程之間保持可見性,可以被多線程同時讀,而且保證不會讀到過時的值,可是隻能被單線程寫(有一種狀況能夠被多線程寫,就是寫入的值不依賴於原值),在get操做裏只須要讀不須要寫共享變量count和value,因此能夠不用加鎖。之因此不會讀到過時的值,是根據Java內存模型的happen before原則,對volatile字段的寫入操做先於讀操做,即便兩個線程同時修改和獲取volatile變量,get操做也能拿到最新的值,這是用volatile替換鎖的經典應用場景。
transient volatile int count; volatile V value;
ConcurrentHashMap的Put操做在定位元素的代碼裏咱們能夠發現定位HashEntry和定位Segment的哈希算法雖然同樣,都與數組的長度減去一相與,可是相與的值不同,定位Segment使用的是元素的hashcode經過再哈希後獲得的值的高位,而定位HashEntry直接使用的是再哈希後的值。其目的是避免兩次哈希後的值同樣,致使元素雖然在Segment裏散列開了,可是卻沒有在HashEntry裏散列開。
hash >>> segmentShift) & segmentMask//定位Segment所使用的hash算法 int index = hash & (tab.length - 1);// 定位HashEntry所使用的hash算法
如何擴容。擴容的時候首先會建立一個兩倍於原容量的數組,而後將原數組裏的元素進行再hash後插入到新的數組裏。爲了高效ConcurrentHashMap不會對整個容器進行擴容,而只對某個segment進行擴容。因爲put方法裏須要對共享變量進行寫入操做,因此爲了線程安全,在操做共享變量時必須得加鎖。Put方法首先定位到Segment,而後在Segment裏進行插入操做。插入操做須要經歷兩個步驟,第一步判斷是否須要對Segment裏的HashEntry數組進行擴容,第二步定位添加元素的位置而後放在HashEntry數組裏。
是否須要擴容。在插入元素前會先判斷Segment裏的HashEntry數組是否超過容量(threshold),若是超過閥值,數組進行擴容。值得一提的是,Segment的擴容判斷比HashMap更恰當,由於HashMap是在插入元素後判斷元素是否已經到達容量的,若是到達了就進行擴容,可是頗有可能擴容以後沒有新元素插入,這時HashMap就進行了一次無效的擴容。
五、HashSet和HashMap的區別
HashSet是基於HashMap實現的。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; } ....... }
HashMap | HashSet |
HashMap實現了Map接口 | HashSet實現了Set接口 |
HashMap儲存鍵值對 | HashSet僅僅存儲對象 |
使用put()方法將元素放入map中 | 使用add()方法將元素放入set中 |
HashMap中使用鍵對象來計算hashcode值 | HashSet使用成員對象來計算hashcode值,對於兩個對象來講hashcode可能相同,因此equals()方法用來判斷對象的相等性,若是兩個對象不一樣的話,那麼返回false |
HashMap比較快,由於是使用惟一的鍵來獲取對象 | HashSet較HashMap |