Hashtable,HashMap,ConcurrentHashMap的原理和区别

参考:https://blog.csdn.net/chenwendangding/article/details/99065623

(1)HashMap不是线程安全的,不支持并发操作,没有同步方法。ConcurrentHashMap是线程安全的,支持并发操作,通过继承 ReentrantLock(JDK1.7的重入锁)/CAS和synchronized(JDK1.8的内置锁)来进行加锁(分段锁),每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 segment 是线程安全的,也就实现了全局的线程安全。
(2)JDK1.8之前HashMap的结构为数组+链表,JDK1.8之后HashMap的结构为数组+链表+红黑树。JDK1.8之前ConcurrentHashMap的结构为segment数组+数组+链表,JDK1.8之后ConcurrentHashMap的结构为数组+链表+红黑树。

1. HashTable

(1)Hashtable的结构是数组+链表实现,无论key还是value都不能为null,是线程安全的,实现线程安全的方式是在修改数据时锁住整个哈希表,效率低,ConcurrentHashMap做了相关优化。
(2)Hashtable的初始size为11,扩容:newsize = olesize*2+1。
(3)计算索引位置index的方法:index = (hashCode & 0x7FFFFFFF) % length。

2.HashMap

(1)JDK1.8之前HashMap的结构
在这里插入图片描述
JDK1.8之前HashMap的结构是数组+链表。HashMap里面是一个数组,数组中的每一个元素是一个单向链表,查找的时间复杂度为O(n)(n为单向链表的长度)。Java7中使用Entry来代表每个数据节点。
(2)JDK1.8之后HashMap的结构
在这里插入图片描述

JDK1.8之后HashMap的结构是数组+链表+红黑树,查找的复杂度降低为O(nlogn)。根据数组元素中,第一个节点的数据类型是Node还是TreeNode来判断该位置是链表还是红黑树。

JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法。因为JDK1.7是用单向链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且循环链表死循环的问题。

HashMap的特点:
(1)HashMap可以存储null键(key)和null值(key),线程不安全,不支持并发操作,没有同步方法。
(2)初始size为16,扩容后的size为newsize = oldsize*2,且size一定为2的n次幂(2^n)。扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入。插入元素后才判断该不该扩容,有可能无效扩容(插入选择扩容,但此时没有再次插入数据,就会产生无效扩容)。当Map中元素总数超过Entry数组大小的75%(负载因子LoadFactor,默认是0.75),就会触发扩容操作,目的是为了减少链表长度,使元素分配更均匀。
(3)计算扩容后数据存储位置即index的方法:index = hashCode & (length – 1)。
(4)设置HashMap的初始值时还要考虑负载因子:
哈希冲突:若干Key的哈希值(hashCode)按数组大小取模(hashCode % length等价于hashCode & (length-1))后,如果落在数组的同一个下标上,将组成一条Entry链(单向链表),对Key的查找需要遍历Entry链上的每个元素并执行equals()比较。
负载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定数组大小为100/0.75=134的。如果希望加快Key查找的时间,还可以进一步降低负载因子,加大HashMap的初始大小,以降低哈希冲突的概率。
(5)HashMap和Hashtable都是采用哈希算法来决定其元素的存储位置,因此HashMap和Hashtable的哈希表包含如下属性:
初始化容量(initial capacity):创建hash表时桶的数量,HashMap允许在构造器中指定初始化容量
容量(capacity):hash表中桶的数量
尺寸(size):当前hash表中记录的数量
负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的散列表,依此类推。轻负载的散列表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢)
负载极限:“负载极限”是一个0~1的数值,“负载极限”决定了hash表的最大填满程度。当hash表中的负载因子达到指定的“负载极限”时,hash表会自动成倍地增加容量(桶的数量),并将原有的对象重新分配,放入新的桶内,这称为rehashing。HashMap和Hashtable的构造器允许指定一个负载极限,HashMap和Hashtable默认的“负载极限”为0.75,这表明当该hash表的3/4已经被填满时,hash表会发生rehashing。“负载极限”的默认值(0.75)是时间和空间上的一种折中:较高的“负载极限”可以降低hash表所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(HashMap的get()与put()方法都要用到查询)。较低的“负载极限”会提高查询数据的性能,但会增加hash表所占用的内存开销。

3. ConcurrentHashMap

(1)JDK1.8之前ConcurrentHashMap的结构
在这里插入图片描述
ConcurrentHashMap结构分为2部分:segment数组,初始化后不可扩容;segment数组内部的数组和链表,内部数组是可以扩容的。
(2)JDK1.8之后ConcurrentHashMap的结构
在这里插入图片描述
JDK1.8之后的ConcurrentHashMap结构和JDK1.8之后的HashMap结构基本上是一样的,也是保持着数组+链表+红黑树的结构,不同的是,ConcurrentHashMap需要保证线程安全。可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近JDK1.8版本的HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来支持并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。

ConcurrentHashMap的特点:
(1)ConcurrentHashMap的底层采用segment数组(分段数组) + 数组+链表实现,是线程安全的,支持并发操作。
(2)通过把整个Map分为N个segment(N称为并发级别/并发数/segment数,默认值是16),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍,也就是说这个时候最多可以同时支持16个线程并发写,只要它们的操作分布在不同的segment上。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值)
(3)Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占;而ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术(分段锁)。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整张表而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。
(4)扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容。
(5)JDK1.8的实现降低了锁的粒度,JDK1.7版本锁的粒度是基于segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)。
(6)JDK1.8版本的数据结构变得更加简单,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要segment这种数据结构了,由于粒度的降低,实现的复杂度也降低了。
(7)JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表。

JDK1.8为什么使用内置锁synchronized来代替JDK1.7中的重入锁ReentrantLock,有以下几点:
答:因为锁的粒度降低了,在低粒度加锁方式而言,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度锁的边界,更加的灵活,而在低粒度中,Condition的优势就没有了。同时,基于原生语法层面的synchronized的优化空间更大,使用内嵌的关键字比使用基于Java API层面的ReentrantLock更加自然。

参考:https://blog.csdn.net/chenwendangding/article/details/99065623

发布了20 篇原创文章 · 获赞 0 · 访问量 463
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览