HashMap、Hashtable、TreeMap、ConcurrentHashMap、SynchronizedMap

Map集合

Map用於保存具有映射關係的數據,因此Map集合裏保存着兩組值,分別是Map裏的Key和Value,Key和Value都可以是任何引用類型的數據。Map的Key不允許重複,即同一個Map對象的任何兩個Key通過equals()方法比較總是返回false。
HashMap和Hashtable都是Map接口的典型實現類,Hashtable是一個古老的Map實現類,現在很少使用了。

HashMap

HashMap的底層是通過數組+鏈表+紅黑樹實現的(JDK8加入了紅黑樹,當存入到數組中的鏈表長度大於8時,即轉爲紅黑樹),數組的每一項都是一個鏈表,每當新建一個HashMap時,就會先初始化一個數組 。HashMap的本質可以理解爲 Entry[ ] 數組。HashMap初始容量大小默認是16,默認加載因子是0.75
HashMap提供了三個構造函數:

  • HashMap():構造一個具有默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap。
  • HashMap(int initialCapacity):構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap。
  • HashMap(int initialCapacity, float loadFactor):構造一個帶指定初始容量和加載因子的空HashMap。

HashMap的部分源碼如下:

public class HashMap<K,V> extends AbstractMap<K,V>
	implements Map<K,V>, Cloneable, Serializable {

	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
	static final int MAXIMUM_CAPACITY = 1 << 30;
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	static final int TREEIFY_THRESHOLD = 8;

	public V get(Object key) {
		Node<K,V> e;
        	return (e = getNode(hash(key), key)) == null ? null : e.value;
	}
	......
}

HashMap源碼中的加載因子:

static final float DEFAULT_LOAD_FACTOR = 0.75f

Hashtable

Hashtable是從JDK1.0就出現的一個Map實現類,它是線程安全的,底層使用synchronized實現,性能比HashMap要低,Hashtable判斷value相等的標準是:value與另外一個對象通過equals()方法比較返回true即可。部分源碼如下:

public class Hashtable<K,V>
	extends Dictionary<K,V>
	implements Map<K,V>, Cloneable, java.io.Serializable {

	private transient Entry<?,?>[] table;
	private transient int count;
	private int threshold;
	private float loadFactor;

	public Hashtable() {
		this(11, 0.75f);
	}
	
	public synchronized Enumeration<K> keys() {
        	return this.<K>getEnumeration(KEYS);
    	}
	
	@SuppressWarnings("unchecked")
	public synchronized V get(Object key) {
		Entry<?,?> tab[] = table;
        	int hash = key.hashCode();
        	int index = (hash & 0x7FFFFFFF) % tab.length;
        	for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        		if ((e.hash == hash) && e.key.equals(key)) {
                		return (V)e.value;
            		}
            	}
        	return null;
	......
}

HashMap與Hashtable的區別(重點)

  • Hashtable是線程安全的,而HashMap是線程不安全的實現,所以HashMap比Hashtable的性能高一點;但是如果有多個線程訪問同一個Map對象時,使用Hashtable會更好。
  • HashMap可以使用null作爲key和value。HashMap裏的Key不允許重複,HashMap最多隻有一個key-value對的key爲null,但可以有無數多個key-value對的value爲null。Hashtable不允許使用null做爲key和value。

注:儘量少使用Hashtable實現類,即使需要創建線程安全的Map類,也無須使用Hashtable類,可以通過Collections工具類的synchronizedMap(new HashMap())方法把HashMap變成線程安全的,也可以使用ConcurrentHashMap

ConcurrentHashMap

ConcurrentHashMap 爲了提高本身的併發能力,在內部採用了一個叫做Segment 的結構,一個 Segment 其實就是一個類 Hash Table 的結構,Segment內部維護了一個鏈表數組,ConcurrentHashMap 定位一個元素的過程需要進行兩次Hash操作,第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的鏈表的頭部,因此,這一種結構的帶來的副作用是 Hash 的過程要比普通的 HashMap 要長,帶來的好處是寫操作的時候可以只對元素所在的 Segment 進行操作即可,不會影響到其他的 Segment,這樣,在最理想的情況下,ConcurrentHashMap 可以最高同時支持 Segment 數量大小的寫操作(剛好這些寫操作都非常平均地分佈在所有的 Segment上),所以,通過這一種結構,ConcurrentHashMap 的併發能力可以大大的提高。

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
	implements ConcurrentMap<K,V>, Serializable {
	
	......

	public ConcurrentHashMap(int initialCapacity, float loadFactor) {
		this(initialCapacity, loadFactor, 1);
	}

	static class Segment<K,V> extends ReentrantLock implements Serializable {
		private static final long serialVersionUID = 2249069246763182397L;
        	final float loadFactor;
        	Segment(float lf) { this.loadFactor = lf; }
	}
	
	......
	
}
  • 爲什麼要用二次hash?
    主要原因是爲了構造分段鎖,使得對於map的修改不會鎖住整個容器,提高併發能力。但是二次hash帶來的問題是整個hash的過程比hashmap單次hash要長,所以,如果不是併發情形,不要使用concurrentHashmap。
  • Segment的結構特徵
    一個Segment裏包含一個Entry數組,每個HashEntry是一個鏈表結構的元素, 每個Segment守護者一個Entry數組裏的元素,當對Entry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。

SynchronizedMap

線程安全的,通過Collections工具類的方法產生。

Map<String,Integer> map = Collections.synchronizedMap(new HashMap<String,Integer>());

TreeMap

TreeMap底層是⼀一個Entry的紅黑樹。

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