2、HashMap中的常量( 五 )

[])new Node[newCap];table = newTab;//如果原来的数组不为空,那么我们就需要把原来数组中的元素重新分配到新的数组中//如果是第2种情况,由于是第一次调用resize,此时数组肯定是空的,因此也就不需要重新分配元素 。if (oldTab != null) {//遍历旧数组for (int j = 0; j < oldCap; ++j) {Node e;//取到当前下标的第一个元素,如果存在,则分三种情况重新分配位置if ((e = oldTab[j]) != null) {oldTab[j] = null;//1.如果当前元素的下一个元素为空,则说明此处只有一个元素//则直接用它的hash()值和新数组的容量取模就可以了,得到新的下标位置 。if (e.next == null)newTab[e.hash & (newCap - 1)] = e;//2.如果是红黑树结构,则拆分红黑树,必要时有可能退化为链表else if (e instanceof TreeNode)((TreeNode)e).split(this, newTab, j, oldCap);//3.到这里说明,这是一个长度大于 1 的普通链表,则需要计算并//判断当前位置的链表是否需要移动到新的位置else { // preserve order// loHead 和 loTail 分别代表链表旧位置的头尾节点Node loHead = null, loTail = null;// hiHead 和 hiTail 分别代表链表移动到新位置的头尾节点Node hiHead = null, hiTail = null;Node next;do {next = e.next;//如果当前元素的hash值和oldCap做与运算为0,则原位置不变if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}//否则,需要移动到新的位置else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);//原位置不变的一条链表,数组下标不变if (loTail != null) {loTail.next = null;newTab[j] = loHead;}//移动到新位置的一条链表,数组下标为原下标加上旧数组的容量if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}
这里还有一个重要的运算需要注意,使用这个判断用于将原来的链表分为两个链表,分别位于原来位置和一个新的位置
//如果当前元素的hash值和oldCap做与运算为0,则原位置不变if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}
这里拿原容量为16,扩容后容量为32为例,判断数组具体位置的(n-1)&key.()
//16容量判断具体位置1111 & key.hashCode()//32容量判断具体位置11111 & key.hashCode()
16容量时是不考虑第5位的值,无论其实1或0都是放在这个位置上,但是现在容量被扩充为32位,需要考虑哈希值的第5位值,如果为0也跟16容量时获得的位置相同,如果为1得到一个新的位置,因此e.hash &直接判断第5位的值即可知道其是否能留在原位置 。其他情况也类似
8、常见的一些面试问题 问题1:jdk1.7的扩容时为什么会出现死循环?
先看看jdk1.7的的扩容代码,核心思路是遍历所有的桶元素并将其插入到适合的位置,插入时使用**头插法**插入 。
void transfer(Entry[] newTable, boolean rehash) {int newCapacity = newTable.length;for(Entry e : table) {while(null != e) {Entry next = e.next;if(rehash) {e.hash = null == e.key ? 0 : hash(e.key);}//计算在新table中的位置int i = indexFor(e.hash, newCapacity);//一下三行就实现了头插法e.next = newTable[i];newTable[i] = e;e = next;}}}

2、HashMap中的常量

文章插图
出现问题的核心位置就是这句:=e.next,当然正常情况下是不会出现问题的,问题只会在多线程操作Map时出现 。
举例说明,假设桶的大小为2,同种的元素如下所示:
假设现在有两个线程:线程1与线程2,这两个线程均需要对进行扩容操作 。
假设线程1先执行扩容方法,在执行完=e.next之后,因为时间片用完被操作系统挂起,轮线程2执行,此时线程1中的e与next分别如上面图所示 。