HashMap、HashTable和ConcurrentHashMap的联系与区别
HashMap基于哈希表的Map接口实现,是以key-value存储形式存在,即主要用来存放键值对。HashMap 的实现不是同步的,这意味着它不是线程安全的。
HashTable和ConcurrentHashMap都是对HashMap的优化,HashMap是线程不安全的集合类,HashTable和ConcurrentHashMap都是线程安全的,且ConcurrentHashMap又是进一步优化的。
🎯回顾Hashmap
-
HashMap 的key、value都可以为null,键位置是唯一的。
-
HashMap中的映射不是有序的。
-
HashMap 的默认初始容量是16。
-
JDK1.8 以后HashMap 使用的数据结构是:数组 + 链表 + 红黑树。 变为红黑树的目的是为了高效的查询。
-
JDK1.8 以后链表与红黑树之间的转换条件:
-
如果链表长度>8时,并且链表个数>64时,才会将链表转换为红黑树。如果链表个数不满足条件只会对数组进行扩容。
-
如果链表长度 < 6时,红黑树又转化为链表。
-
HashTable和ConcurrentHashMap的键和值不能为null
,因为在并发的环境下, 通过get(key)的时候,你不知道是因为key为null而返回的null还是之前put进去的值为null.
🔭HashMap结构图:
HashTable与ConcurrentHashMap(JDK1.8 )区别
🎯1.加锁策略
HashTable是对通过在其所有公共方法上使用 synchronized 关键字来实现线程安全。这意味着在任何时候,只有一个线程可以访问 HashTable 的状态。
ConcurrentHashMap抛弃了原有的 jdk1.7版本的Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。在 jdk1.8 中,ConcurrentHashMap 的内部结构改为了一个 Node 数组,每个 Node 是一个链表或红黑树(在链表长度超过一定阈值时转换为红黑树)。put方法,当数组中被Hash的位置为null,使用CAS新建Node对象,写入数组对应的位置,当数组中指定位置不为null时,通过synchronized,把Node节点添加入数组(链表<8)或者红黑树(链表>=8)。
虽然HashTable与ConcurrentHashMap都保证了线程安全,但是前者会造成多余的竞争锁,效率自然下降。
🔍注意:
HashTable的底层数据结构是: 数组 + 链表
ConcurrentHashMap(jdk1.8)的底层数据结构是: 数组 + 链表 + 红黑树
🎯2.扩容策略
HashTable:和HashMap的扩容方式相同,先申请一个更大的空间,然后将原来的数据直接拷贝过去,相当于获得一个新的对象。
ConcurrentHashMap:先申请一个更大的空间,但是不直接全部拷贝过去,只是拷贝一部分,每次对数组操作都会拷贝一部分。