1、数据结构
1.1、jdk1.7 使用数组+链表的结构, 链表采用的是头插法(多线程下会有形成循环链表的危险)
1.2、jdk1.8 使用数组+链表+红黑树的结构, 链表和红黑树是使用的尾插法
2、1.7 与 1.8的差别以及为什么这样设计?
2.1、在数据结构上 1.7时使用的是 数组+链表 这种数据结构,1.8时使用的是 数组+链表+红黑树 的数据结构,减少hash冲突情况下的寻址时间
2.2、在hash定位的时候, 1.7直接使用key的hash值 & n-1进行与或位运算,1.8 则先将hash进行了一次扰动处理,key的hash ^ hash>>>16位后再与 n-1 进行与运算,使hash分布更为均匀减少hash冲突
2.3、再扩容动作上 链表上的数据插入方式 从1.7头插法改为1.8的尾插法,解决了在并发情况下resize导致的链表循环指针问题
3、数据结构为什么改为 数组+链表+红黑树 呢
当每个mapentry中链表长度大于8时(且容量大于64), 会由链表转换为红黑树(减少hash冲突情况下的寻址时间复杂度 O(n)降低为O(logn)
4、为什么选择8这个临界值?
根据泊松分布,链表长度大于8的情况非常小,极大多数的情况都可以被链表的情况覆盖
在长度小于8的时候,链表和红黑树的性能相差不多,但是转化为树还需要时间和空间
当mapentery中的红黑树长度降低小于6的时候,会由红黑树转回为链表结构(6和8起到了很好的缓冲作用, 避免了频繁的数据结构转换
5、为什么选择红黑树而不是二叉树呢?
5.1、普通二叉树会出现节点分布及其不平衡的情况
5.2、AVL和平衡二叉树之间差别
AVL树更加平衡,可以提供更快的查询能力,适合查找密集形的任务
红黑树适合插入密集型任务,在插入的时候使用较少的旋转
6、为什么对hash值进行一次干扰呢
6.1、对n-1做与运算的时候,n通常比较小,n-1的二进制,大概率高位补0,这样做与运算的时候key对应hash值的前16相当于从来都不参与计算,
通过右移再进行异或的操作,使后16位具有了前16位的特征,使hash分布更为均匀
7、为什么使用尾插法
hashmap分为两步
-
扩容:创建一个新的Entry空数组,长度是原数组的2倍。
-
ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
在头插法的情况下并发扩容存在链表成环的这种情况,改为尾插法避免了这种情况