问题:
1.hashmap底层实现原理?数据结构?
2.size为什么是2的n次幂?
3.put实现?
4.hashmap为了解决hash冲突做了哪些事情?
5.扩容机制?
6.扩容之后,新旧table数组数据怎么迁移?迁移过程中做了哪些操作保证了node的均匀分布?
解答:
计算key的hashcode(hash值计算采用的是高16位和hah与或^操作,为了让高16位也参与hash算法,目的是为了让node分布均匀,减少hash冲突的概率),
1.计算key在table =node[]中的下标:(用key的hash%size-1,源码中采用的巧妙的算法,用&代替%,因为%操作相对于&操作效率较低:前提是table.size必须是2的n次幂)
计算出key的下标位置后,有两种情况:
如果没有发生碰撞,直接添加元素到散列表中去
如果发生了碰撞(hashCode值相同),进行三种判断
1:若key地址相同或者equals后内容相同,则替换旧值
2:如果是红黑树结构,就调用树的插入方法
3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阀值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
如果桶满了大于阀值,则resize进行扩容
为什么HashMap的默认负载因子是0.75,而不是0.5或者是整数1呢?
答案有两种:
阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 根据HashMap的扩容机制,他会保证容量(capacity)的值永远都是2的幂 为了保证负载因子x容量的结果是一个整数,这个值是0.75(4/3)比较合理,因为这个数和任何2的次幂乘积结果都是整数。
理论上来讲,负载因子越大,导致哈希冲突的概率也就越大,负载因子越小,费的空间也就越大,这是一个无法避免的利弊关系,所以通过一个简单的数学推理,可以测算出这个数值在0.75左右是比较合理的
扩容:
调用场景:
1.初始化数组table
2.当数组table的size达到阙值时即++size > load factor * capacity 时,也是在putVal函数中
实现过程:
1.通过判断旧数组的容量是否大于0来判断数组是否初始化过
否:进行初始化
-
判断是否调用无参构造器,
-
是:使用默认的大小和阙值
-
否:使用构造函数中初始化的容量,当然这个容量是经过tableSizefor计算后的2的次幂数
-
是,进行扩容,扩容成两倍(小于最大值的情况下),之后在进行将元素重新进行与运算复制到新的散列表(这里的key的hash需要重新计算hash,具体也是用hash&(newTable.length-1)这样保证了在原来oldTable中相同下标的Node链表,在迁移到新的newTable时,原来的低位链还是在原来的下标(i),而高位链则是在(i+newTable.length)位置上,这样Node相对分布均匀,减少了hash冲突的概率)
概括的讲:扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。