Java Map Summary

Java Map Summary

一、概要

Map 9个

类名 since 线程安全 key null value null 特点
Map 1.2
Hashtable 1.0 Yes No No 1.2版本实现Map接口
对象级同步锁
未做处理的Hash算法
链表头插法处理哈希碰撞
HashMap 1.2 No Yes Yes 高位扰动的Hash算法
树化临界值8来源于泊松分布的计算
负载因子的权衡考虑
非完全移动的扩容算法
多线程情况下,1.7版本的头插法扩容机制会导致死循环,1.8版本的红黑树会导致根节点互相引用
TreeMap 1.2 No No Yes 红黑树结构
key可排序
WeakHashMap 1.2 No Yes Yes key为null的掩码处理
弱引用存储key
LinkedHashMap 1.4 No Yes Yes 继承自HashMap
通过前后节点维护key的添加顺序
IdentityHashMap 1.4 No Yes Yes key为null的掩码处理
通过System.identityHashCode(since 1.1)计算Hash
通过==比较key是否相等
适合用于序列化、深度复制或记录对象代理
EnumMap 1.5 No No Yes 用于枚举类型键的专用Map实现
value为null的掩码处理
基于数组存储元素
ConcurrentHashMap 1.5 Yes No No 1.7及之前版本使用分段锁,继承自ReentrantLock,初始化后无法更改分段数量,锁的粒度大,1.8版本保留代码片段是为保证序列化兼容性
put方法通过CAS自旋初始化表和头节点,synchronized锁头节点(JVM自动优化)
get方法不加锁,通过getObjectVolatile获取Node,Node内同样是volatile字段
JDK1.5版本引入操作系统的CAS特性,JDK1.6版本优化synchronized,实现轻量级锁,重量级锁由操作系统的mutex lock实现
ConcurrentSkipListMap 1.6 Yes No No 排序规则与TreeMap相同,线程安全且吞吐量更高
基与skipList实现
通过CAS自旋保证线程安全

二、探究

1、父类不同

Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类,但二者都实现了Map接口。

ConcurrentHashMap继承自AbstractMap类,实现了ConcurrentMap。

2、线程安全性

Hashtable 中的方法是synchronized的,而HashMap中的方法在缺省情况下是非synchronized的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。

总结一句话:Hashtable(1.0版本)不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。

HashTable和ConcurrentHashMap都是线程安全的?它们之间有什么区别呢?

HashMap在多线程下会发生什么呢?

3、是否提供contains方法

HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。
Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。

4、key和value是否允许null值

Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常。

HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。

ConcurrentHashMap中,key和value也都不允许出现null值。

5、遍历的内部实现方式不同

Hashtable、HashMap、ConcurrentHashMap都使用了 Iterator。但由于历史原因,Hashtable还使用了Enumeration的方式 。

6、数组初始化和扩容方式不同

HashTable在不指定容量的情况下的默认容量为11,而HashMap、ConcurrentHashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap、ConcurrentHashMap则要求一定为2的整数次幂。
具体扩容时,Hashtable将容量变为原来的2倍加1,而HashMap、ConcurrentHashMap扩容时,将容量变为原来的2倍。

容量是2的自然数次幂有什么好处呢?

Hash值的处理,高位运算?

7、HashTable和ConcurrentHashMap都是线程安全的?它们之间有什么区别呢?

  • HashTable

HashTable对于读写操作都会加锁,而且是方法级别的锁,锁的粒度非常大,虽然可以保证线程安全性,但是效率要打个折扣。

  • ConcurrentHashMap

ConcurrentHashMap只对写操作加锁(synchronized),而且只锁头节点(对于非头节点元素),锁的粒度小了很多,对于读操作是不加锁的。为了保证读操作不读到脏数据,所以使用了volatile关键字,保证数据可见性。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
}	

对于头节点元素,采用CAS操作加入头节点

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
        break;                   // no lock when adding to empty bin
}

新增完节点马上解锁,当然后面还有增加数量(CAS)、扩容的过程,但是扩容的锁的粒度也很低,生成nextTable时是没有锁的,直到移动元素的时候才加锁,参考 transfer() 方法。

8、HashMap在多线程下会发生什么呢?

多线程put操作后,形成环状结构,导致读取时线程进入死循环。

多线程put操作,线程操作覆盖,导致元素丢失。

9、容量是2的自然数次幂有什么好处呢?

假如容量b是2的自然次数幂。

计算hash位置的时候,可以用一个公式。

a % b = a & (b-1);

在扩容的时候,这个容量也有用,大大减少了需要移动的元素。

Node<K,V> loHead = null, loTail = nul
Node<K,V> hiHead = null, hiTail = nul
Node<K,V> next;
do {
    next = e.next;
    // 判断元素是否需要移动位置
    if ((e.hash & oldCap) == 0) {
        if (loTail == null)
            loHead = e;
        else
            loTail.next = e;
        loTail = e;
    }
    else {
        if (hiTail == null)
            hiHead = e;
        else
            hiTail.next = e;
        hiTail = e;
    }
} while ((e = next) != null);
if (loTail != null) {
    loTail.next = null;
    newTab[j] = loHead;
}
if (hiTail != null) {
    hiTail.next = null;
    // 需要移动位置的元素,只需要用j + oldCap就可以确定新位置
    newTab[j + oldCap] = hiHead;
}

10、Hash值的处理

  • HashTable

压根没处理

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
  • HashMap

扰动计算 减少冲突

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • IdentityHashMap
private static int hash(Object x, int length) {
    int h = System.identityHashCode(x);
    // Multiply by -127, and left-shift to use least bit as part of hash
    return ((h << 1) - (h << 8)) & (length - 1);
}
  • ConcurrentHashMap

扰动计算 减少冲突

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}
posted on 2023-03-09 21:38  共感的艺术  阅读(19)  评论(0编辑  收藏  举报