HashMap解析
1.Hashmap在插入元素过多的时候需要进行Resize,Resize的条件是:
HashMap.Size > Capacity * LoadFactor。
2.Hashmap的Resize包含扩容和ReHash两个步骤,ReHash在并发的情况下可能会形成链表环(头插)。
3.1.8后采用尾插(对树的操作会出现死循环,但不会有链表环),并采用了红黑树(节点>8且table的长度>=64)。
JDK1.8版本之前扩容的死循环、数据丢失问题
以下为1.8之前的扩容代码,
e.next = newTable[i];newTable[i] = e;
可以看出链表采用的是头插法。来看下死循环是怎么形成的:
假设遍历链表A -> B -> null,两个线程,遍历元素A,线程1运行完
Entry<K,V> next = e.next
这句后暂停(那么e:A,e.next:B);线程2运行,同样遍历A -> B,运行完后,即为:head -> B -> A -> null;
线程1继续运行
e.next = newTable[i];newTable[i] = e;e = next
,循环到A:
A -> B
head -> A
形成循环:head -> A -> B -> A -> B …
循环到B:
B -> A
head -> B
形成循环:head -> B -> A -> B …
然后交替循环A、B,死循环了。
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
JDK1.8 HashMap
hash()
h = key.hashCode,h ^ (h>>>16) 用到了
异或
运算。首先h相等时,hash也是相等的。
当 h < 2^16 时,h >>> 16等于0,而0与h作异或运算,hash = h。
当 h >= 2^16 时,hash肯定不等于h。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
resize()
我们知道HashMap是数组+链表结构 +红黑树,这里只讨论链表。假设初始容量为默认的oldCap = 16。
1.遍历数组桶,如果里面装的链表结构只有一个节点,那么重新分配。
if (e.next == null) newTab[e.hash & (newCap - 1)] = e;
2.如果链表有多个节点,比如A -> B -> C -> null,那么就要把ABC分为 低位链表(loHead、loTail) 和
高位链表(hiHead、hiTail),假设A不动,还是原来桶的位置,而B、C属于高位,那么B->C(这里是尾插)
这个高位链表就会插入到新桶位置为:旧桶位置 + 16(
newTab[j + oldCap] = hiHead;
ÿ