本文不介绍HashMap的主要知识,而是
1.resize
2.线程为何不安全与fail-fast机制
3.concurrenthashmap在1.8的实现
4.容量为何要2次幂
resize
我以前认为是全部重新落桶, 其实深入思考下,不是这样的
他的hashcode比如010101010
不考虑resize,在put的时候会先(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
进行扰动
比如扰动成1010101010
在真正执行put的时候,如果容量为32位,会取最后5个数
扩容了就多取一位
这个时候,数组上冲突的元素,有一些多取的那一位是0,有一些是1,如果是0,意味着这个节点的取的值不变,所以在数组中的位置不变,但是如果是1,就需要增加扩容前容量的长度
对于1.7的链表,就是简单的移除,然后增加到另一段链表上,如果有的话
对于1.8的红黑树,需要移除一个节点,然后维持平衡,然后增加到另一段链表/红黑树上, 然后维持平衡,如果有的话
线程为何不安全
在resize的时候,两个线程会创建两个数组,然后执行了一些逻辑,使得某数组的链表上形成了循环链表,最后也使用了这个数组作为真正采用的数组。然后在后来的put值的时候,遍历到了链表,然后在node.next的时候陷入了无限循环。
fail-fast
使用迭代器的时候,内部维护修改次数,如果不符合,就直接抛出异常。
ConcurrentHashMap
还是不想了解太多,1.8cas+锁单个数组位上的东西。
容量为何为2次幂
原因有多方面
1.2倍适中,既不频繁扩容,也不浪费空间
如果你保守地新增了数组的长度,面对数据量快速增长的情况下,扩容会变得十分频繁,2倍是非常省力的。但是为什么不是3倍?4倍?这样一来又显得浪费。能不能处于2倍之下的1点几倍?下面会解答
2.位运算(核心)
当你计算出来hashcode,正常思维是对数组长度取模,但是这样运算效率太低了!位运算可以直接截取后面若干位,直接得到结果。并且位运算可以很方便地进行扰动。