HashMap原理:
底层结构: 数组+链表+红黑树
put()方法:
- key值经过hash函数计算与数组长度减一做&运算,定位数组位置,如果当前hash桶有值,则equals比较,不同则存入链表,如果链表长度大于8且数据总量大于64,则转为红黑树,当数组占用达到长度乘以阈值时进行2倍扩容
get()方法:
- 计算key值hash与数组长度减一取模,定位数组位置,之后通过equals方法挨个比较链表或红黑树的节点,相同则返回对应value
为什么底层使用红黑树结构?
- 红黑树是一种类平衡二叉树,查找元素时使用二分查找,logn的复杂度,而链表是n的复杂度,数据越多,性能优势越明显
为什么不直接使用数组+红黑树?
- 数据少的时候,hash冲突少,树的查找优势不明显,同时每次插入,更新都要旋转树,也是一种性能开销
为什么HashMap底层结构用红黑树而不是AVL树?
- 二叉树在一些极端情况下容易发生数据倾斜,无法保证性能;AVL平衡二叉树,每次插入更新都要旋转树结构,开销很大;红黑树不追求完全平衡,可以在保证大致平衡的基础上减少旋转的开销,最多三次旋转,而查询最多也只是多比较一次而已
ConcurrentHashMap
1.7版本前
结构:Segment数组 + HashEntry数组
原理:Segment是ConcurrentHashMap的一个内部类,他继承了ReentrantLock,所以是一个可重入锁,segment默认为16,每个segment上一把锁,所以支持最大并发数是16
每个segment下有多个HashhEntry,锁的粒度还是不够细
jdk1.8版本
结构:数组 + 链表 + 红黑树
原理:抛弃了原有的分段锁,采用了CAS+synchronized实现了更加细粒度的锁,每个node上一把锁,node为链表头结点或红黑树的根节点,锁的粒度比较细,可以很好的支持并发编程,
问题:
为什么1.8版本要使用内置锁synchronized代替reetrantLock可重入锁?
1.6版本,synchronized进行了重大升级,从无锁到偏向锁到轻量锁到重量级锁
ConcurrentHashMap的get方法要不要加锁?
get不需要加锁,因为Node的元素value和指针Next是用volatile修饰的,所以A修改节点的value或者新增节点的时候对B线程来说是可见的
get方法不需要加锁与volatile修饰的hash桶数组有关吗?
没关系,hash桶数组table是用volatile修饰主要是保证在数组扩容的时候保证可见性
ConcurrentHashMap为什么不支持key或value为null?
因为如果允许value为null,那么调用get方法的时候返回一个null,你不知道是value为null,还是没找到key
ConcurrentHashMap的并发度是多少?
由于是以每个hash桶的数组元素做为锁的粒度,所以它的并发度就是数组长度
ConcurrentHashMap的迭代器是强一致性还是弱一致性?
不同于HashMap,它是弱一致性,迭代器创建后会按照hash表结构遍历每一个元素,遍历过程中元素是可以变化的,如果是遍历过的那就体现不出来,没有遍历过的就可以体现出来,这就是弱一致性。
ConcurrentHashMap和HashTable哪个效率更高?
ConcurrentHashMap效率更高,HashTable是把整个hash表加锁,而ConcurrentHashMap是以每个数组元素为单位通过CAS和synchronized进行加锁,锁的粒度更小。
HashTable锁的机制是怎样的?
HashTable是使用synchronized来实现线程安全的,给整个hash表加了一把大锁,多线程访问,只有一个线程能获取锁,这个性能就差很多
多线程下还有其他安全的操作map的方法吗?
有Collections.synchronizedMap方法,但是它的本质也是对hashmap进行了封装,也是全表锁,多线程环境下性能很差
参考博客链接:
https://blog.csdn.net/yunzhaji3762/article/details/113623168