这是一道阿里的面试题,考察你对HashMap源码的了解情况,废话不多说,咱们就直接上源码吧!
jdk 1.7 源码
void resize(
int newCapacity) {
Entry[] oldTable = table;
//保存旧数组
int oldCapacity =
oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
//判断当前数组大小是否达到最大值
threshold =
Integer.MAX_VALUE;
return;
}
Entry[] newTable =
new Entry[newCapacity];
//创建一个新数组
boolean oldAltHashing =
useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >=
Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
//是否需要重新计算hash值
transfer(newTable, rehash);
//将oldTable的元素迁移到newTable
table = newTable;
//将新数组重新赋值
//重新计算阈值
threshold = (
int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1
);
}
void transfer(Entry[] newTable,
boolean rehash) {
int newCapacity =
newTable.length;
for (Entry<K,V> e : table) {
//遍历oldTable迁移元素到newTable
while(
null != e) {
//①处会导致闭环,从而导致e永远不为空,然后死循环,内存直接爆了
Entry<K,V> next =
e.next;
if (rehash) {
//是否需要重新计算hash值
e.hash =
null == e.key ? 0
: hash(e.key);
}
int i =
indexFor(e.hash, newCapacity);
e.next = newTable[i];
//①
newTable[i] = e;
//①
e = next;
//①
}
}
}
jdk 1.8 源码(比较长,慢慢品哈)
final Node<K,V>
[] resize() {
Node<K,V>[] oldTab = table;
//保存旧数组
int oldCap = (oldTab ==
null) ? 0
: oldTab.length;
int oldThr = threshold;
//保存旧阈值
int newCap, newThr = 0;
//创建新的数组大小、新的阈值
if (oldCap > 0
) {
if (oldCap >= MAXIMUM_CAPACITY) {
//判断当前数组大小是否达到最大值
threshold =
Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >=
DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
//扩容两倍的阈值
}
else if (oldThr > 0)
// 初始化新的数组大小
newCap =
oldThr;
else {
//上面条件都不满足,则使用默认值
newCap =
DEFAULT_INITIAL_CAPACITY;
newThr = (
int)(DEFAULT_LOAD_FACTOR *
DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
//初始化新的阈值
float ft = (
float)newCap *
loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (
float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
//将新阈值赋值到当前对象
@SuppressWarnings({"rawtypes","unchecked"
})
//创建一个newCap大小的数组Node
Node<K,V>[] newTab = (Node<K,V>[])
new Node[newCap];
table =
newTab;
if (oldTab !=
null) {
for (
int j = 0; j < oldCap; ++j) {
//遍历旧的数组
Node<K,V>
e;
if ((e = oldTab[j]) !=
null) {
oldTab[j] =
null;
//释放空间
if (e.next ==
null)
//如果旧数组中e后面没有元素,则直接计算新数组的位置存放
newTab[e.hash & (newCap - 1)] =
e;
else if (e
instanceof TreeNode)
//如果是红黑树则单独处理
((TreeNode<K,V>)e).split(
this, newTab, j, oldCap);
else {
//链表结构逻辑,解决hash冲突
Node<K,V> loHead =
null, loTail =
null;
Node<K,V> hiHead =
null, hiTail =
null;
Node<K,V>
next;
do {
next =
e.next;
if ((e.hash & oldCap) == 0
) {
if (loTail ==
null)
loHead =
e;
else
loTail.next =
e;
loTail =
e;
}
else {
if (hiTail ==
null)
hiHead =
e;
else
hiTail.next =
e;
hiTail =
e;
}
} while ((e = next) !=
null);
//原索引放入数组中
if (loTail !=
null) {
loTail.next =
null;
newTab[j] =
loHead;
}
//原索引+oldCap放入数组中
if (hiTail !=
null) {
hiTail.next =
null;
newTab[j + oldCap] = hiHead;
//jdk1.8优化的点
}
}
}
}
}
return newTab;
}
总结
jdk1.7扩容是重新计算hash;jdk1.8是要看看原来的hash值新增的那个bit是1还是0好了,如果是0则索引没变,如果是1则索引变成"原索引+oldCap".这是jdk1.8的亮点,设计的确实非常的巧妙,即省去了重新计算hash值得时间,又均匀的把之前的冲突的节点分散到新的数组bucket上
jdk1.7在rehash的时候,旧链表迁移到新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是jdk1.8不会倒置