底层数据结构
1.7及之前,数组+链表
1.8及之后,数组+链表+红黑树
初始容量指是table的大小, 负载系数决定了自动扩容的临界值。
插入元素是怎么操作的
对插入的key通过hash函数获得hash值,然后hash值与数组长度length减一进行与运算,即hash &(length-1),获得数组的插入位置,然后开始遍历链表或者红黑树节点,先判断key是否存在,若存在,则直接返回。
如果不存在,进行链表的插入(头插法)或者红黑树的插入操作。
为什么数组的长度是2的整数次幂
加快哈希计算和减少哈希冲突,因为取模操作不如与操作速度快, 而2的整数次幂减1,一定是个奇数,奇数转换成二进制最低位一定是1, 所以hash值与它进行与操作,最低一位可能是1或者0,这样保证了散列的均匀性。
什么时候扩容,扩容后数组的大小是原来的多少倍
当entry或者链表的长度超过了初始容量*负载系数的时候,开始要扩容了,扩容后是原来的2倍,然后开始进行数据迁移,从旧数组拷贝到新数组里。
为什么链表的长度达到8的时候,会转换为红黑树?(数组的长度大于64,否则进行扩容)
链表的时间复杂度是O(N), 红黑树的时间复杂度是O(logN), 且树化的过程需要时间和空间的代价。这里选择8作为转换的临界值,是参考了珀松分布,在一个良好的hash算法里,冲突链表的长度达到8的概率只有亿分之6,这是极低的概率。
所以通过上面的结论,当长度达到8的时候,链表的平均查找长度是8/2 = 4,红黑树是log2(8) = 3 4 > 3
红黑树虽然查询效率高于链表,但是节点占用空间大,只有达到一定树木才有树化的意义,基于时间和空间的平衡考虑。链表过长而数组过短,导致hash碰撞发生的可能性越大,这个时候树化治标不治本,所以如果长度小于64,对数组进行扩容,而不是树化。阈值是默认数组大小乘以负载因子,如果声明了大小,则把阈值设定为刚大于设定值的2的次方(比如说设定的大小是1000,那阈值就是1024),然后在resize方法中,先把阈值赋给容量大小,然后在把容量大小*0.75在赋值给阈值。