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的红黑树。

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