关于HashMap的一些疑问与解答
1.为什么TREEIFY_THRESHOLD要是8?
treefy是有成本的,新增或删除元素时有额外的操作,同时TreeNode是普通Node体积的二倍,因而需要一个平衡点。
随机hashcode下符合泊松分布,
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
一个桶内8个元素的概率为一亿分之六;
当情况发生时,说明hashcode设计的不合理,此时会有大量碰撞,这种情况下维护树就是有必要的。
这个值越小,树化的影响就越强力,当小于8时,就可能导致频繁的树化,增加维护成本。
普通Node内部维护的属性有:
int hash,
K key,
V val,
Node<K,V> next
TreeNode维护的属性有:
int hash,
K key,
V val,
Node<K,V> next
以及
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
大约是普通Node大小的2倍
2.为什么put方法中的hash值要再进行一次加工,为何要使用 ( h = k.hashCode() ) ^ ( h >>> 16 ) 操作?
计算桶位置的方法为 (tab.length - 1) & hash
一般来说,hashmap的长度不会过大,不会超过65536(1 << 16),只有hash的低16(左右)位参与计算,如果直接将 k.hashCode() 当作hash进行位置计算的话,
则其中的高16(左右)位信息并未参与计算,而 ( h = k.hashCode() ) ^ ( h >>> 16 ) 算法将高16位中的信息加以利用,得出低16位更加随机的hash,
从而使桶的分布更佳均匀,使用^运算而不是&或者|,是因为^不会使结果有概率上的偏向性。
tab.length固定为2的次方,二进制即为最高位有效位为1,
其余位均为0的数字,tab.length - 1即为所有有效位均为1的二进制数字.
hash对其求&,则获得的值x >= 0 且x <= tab.length - 1,共 tab.length 个,且分布完全依赖于hash的与tab.length - 1位数相同的低位。
3.扩容时扩大多少,为什么?
容量扩大为原来的二倍( oldCap << 1 ),该方法配合寻桶算法 hash & (newCap - 1) 使用,
使得扩容后元素在新数组中分布均匀(newCap - 1二进制最高有效位对应的hash相应的位如果为1,则桶位置下标为(原来的下表 + oldCap),若为0,则桶位置不变,
0,1是依赖于hashcode的,随机的,所以理论上是均匀的)