HashMap基础知识
HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。JDK1.8 之前 HashMap 由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。
HashMap的put操作
HashMap定位存储下标的方法
static final int hash(Object key) { //jdk1.8 & jdk1.7
in th;
// h = key.hashCode() 为第一步 取hashCode值
// h ^ (h >>> 16) 为第二步 高位参与运算
return(key == null) ? 0: (h = key.hashCode()) ^ (h >>> 16);
}
static int indexFor(int h,int length) { //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的
return h & (length-1); //第三步 取模运算
}
resize的过程
// 下面这段就是把原来table里面的值全部搬到新的table里面
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
// 这里注意, table中存放的只是Node的引用, 这里将oldTab[j]=null只是清除旧表的引用, 但是真正的node节点还在, 只是现在由e指向它
oldTab[j] = null;
// 如果该存储桶里面只有一个bin, 就直接将它放到新表的目标位置
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 如果该存储桶里面存的是红黑树, 则拆分树
else if (e instanceof TreeNode)
//红黑树的部分以后有机会再讲吧
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 下面这段代码很精妙, 我们单独分一段详细来讲
else { // preserve order
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;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
首先,定义了两个新的链表分别是: lo 链表与 hi 链表,其中 loHead 与 hiHead 指向头部, loTail 与 hiTail 指向尾部。然后,从旧 table 的某个桶的头部开始,逐一将桶内链表上的每一个节点迁移到 lo 链表或 hi 链表中。决定进入 lo 链表或 hi 链表的依据是判断条件:(e.hash & oldCap) == 0。下面解释下为什么采用这种判断依据。首先, oldCap 的大小必然是2的n次幂,newCap 的大小必然是2的n+1次幂,在求桶的下标时使用公式 (capacity-1)&hash ,这个公式本质上取出了 hash值的低 n 位,这个 n 对应于 n 次幂。然而,当迁移时需要计算在新 table 下的桶下标,这是使用的公式是 (2*capacity-1) & hash ,这样就会比原来多取出一位,而二进制只有0/1,这也与两个新链表 hi/lo 对应。(e.hash & oldCap) == 0时,这个节点应该被丢进 lo 链表,否则进入 hi 链表。在将原 table 上某个桶的一个链表全部拆完后,将这两个链表分别链接到新 table 对应的桶中,而这两个桶下标间存在一种数值上的关系,即二者差为一个原来的 table 长度——oldCap。