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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-03-09 人形词云,根据图片黑白形状绘制词云
2020-03-09 汉化的simple词云
2020-03-09 对于数据的平均值处理
2020-03-09 文字替换成函数返回数字进行排序
2020-03-09 校验
2020-03-09 Python:使用lambda对列表(list)和字典(dict)排序
2020-03-09 python中的数据排序