花点时间通过读写方法对Map接口的这三个实现类做出比较:Hashtable,HashMap,ConcurrentHashMap
Hashtable是比较老的实现,HashMap对Hashtable做了优化,ConcurrentHashMap是HashMap的高并发实现。
以Put方法为例,以下是三者的实现及部分注释。
HashMap
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
/*获取Map容量,未初始化(tab==null)则进行初始化*/
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
/*通过hash与容量tabsize做与(&)运算获取index*/
/*此处与Hashtable不同,后者是取模运算,效率低一些*/
/*此时如果index处没有数据,则直接创建节点插入*/
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
/*比较index处的头节点(hash、key)*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
/*在同一节点处挂的数据达到一定数量时会转化为树结构*/
/*向树中插入一个节点*/
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
/*遍历联表找到尾结点*/
/*将数据节点插入到链表尾部或者覆盖节点数据*/
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
/*返回插入结果*/
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
/*如果达到最大容量则扩容,每次扩容为之前2倍*/
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
可以看出HashMap的主要特点
1. 非线程安全
2. 允许key和value的null值
3. 对Key的哈希值做哈希扩散/二次哈希,减少哈希碰撞
4. 节点数据超过1个使用单向链表存储,超过8个转化为红黑树(JDK1.8优化)。
5. 达到最大容量则扩容,容量为之前2倍。
6. 初始容量为16。
Hashtable
/*对整个方法做同步*/
public synchronized V put(K key, V value) {
/*value不允许为null*/
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
/*计算key的hash值*/
int hash = key.hashCode();
/*将hash值对容量取模运算*/
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
/*遍历链表找到要覆盖的值*/
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
/*没找到值,将值插入到链表尾部*/
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
/*如果容量超限,则扩容*/
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
可以看出Hashtable有以下特点:
1. 线程安全,对整个Put方法同步,写效率较低。
2. 不接受null值
2. 直接哈希并且取模运算找index,比较老的实现,效率不高。
3. 节点数据超过1个使用单向链表保存。
4. 达到最大容量时扩容,扩容后的数据位之前2倍+1。
5. 初始容量为默认11,负载因子0.75. 和HashMap和ConcurrentHashMap不同的是,Hashtable容量可以是任意正整数的值,而另外两个则必须是2的幂数。
ConcurrentHashMap
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
/*spread做哈希扩散,减少哈希碰撞*/
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
/*初始容量为16*/
if (tab == null || (n = tab.length) == 0)
tab = initTable();
/*插入新节点,作为表头,加乐观锁(CAS)*/
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
/*正在进行rehash*/
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
/*对单条数据加锁*/
synchronized (f) {
if (tabAt(tab, i) == f) {
/*hash值大于0,链表类型,遍历新增数据或者覆盖旧数据*/
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
/*红黑树,遍历插入新及诶单或者覆盖旧数据*/
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
/*大于阈值(8),转化为树*/
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
ConcurrentHashMap是HashMap的高并发实现,除HashMap的特性以外,有以下特点:
1. 线程安全,对单条数据同步,效率高
2. 不接受null值
三者的写操作逻辑区别不大,都是通过哈希值确定节点索引再遍历搜索,仍然是哈希算法和遍历的数据结构方面的区别。
此外还有几个相关的Map类型
- CheckedMap, 对Map的封装,指定和检查传入的参数类型
- IdentifyHashMap, 使用“引用相等”而非“对象相等”
- LinkedHashMap,记录元素顺序,可实现LRU,内部使用双向链表,有性能损耗
- SynchronizedMap,HashMap的同步包装类
- UnModifiableMap,对Map的只读封装
- WeakHashMap,存储弱键,内部有个ReferenceQueue,保存被回收的Key,从Map中移除
写的比较简单,仅做记录。