JDK1.8 HashMap
特点
基于哈希、数组、链表实现,存储KV格式数据,不允许K重复 ;
基于源码,探究下底层的存储逻辑和数据结构
putVal方法
key发生碰撞时,代码逻辑
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key
|| (key != null && key.equals(k))))
break;
p = e;
}
1.如果key值都相等,则替换原key对应的value
2.如果key值不相等,将key/value添加到链表最后;判断链表长度是否超过默认值(8),如果超过则treeifyBin,该方法会尝试触发resize,无法resize时,则将链表中的元素类型由Node(Map.Entry)替换成TreeNode(LinkedHashMap.Entry)
resize方法
- 初始化
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
初始化大小为16,threshold为12,当填充的key个数达到threshold个,则触发扩容
- 扩容逻辑
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; // double threshold
}
...
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY
&& ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
MAXIMUM_CAPACITY的默认值是 230 2 30 ,所以容量上线是介于 230 2 30 与 231−1 2 31 − 1 之间;在容量超过 230 2 30 之前,每次扩容一倍
- oldTab复制逻辑
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)
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;
}
}
}
}
}
将oldTab中的数据复制到newTab中,过程:
- 依次取出oldTab中每个元素,判断元素类型
- 如果不含子节点,则重算角标(e.hash & (newCap - 1))后置入newTab
如果包含子节点,则需要对整个节点链的每一个元素重算角标,基于(e.hash & oldCap) == 0分割节点链(原因参见下文 n-1&h),最多生成两组节点链,每条节点链的相对顺序与oldTab中保持一致(A->B->C,那么只要保证C在A后面,就跟原链顺序一致)
n-1&h
当n=16,n-1=15,h取值0,16,48,计算结果:
0&1111=0
10000&1111=0
110000&1111=0
可以看出0,16,48的计算结果都是0,所有哈希值是这三个的元素会被放到一个节点链中(HashMap中);取值时依然基于n-1&h就能确定角标;
当n=32,n-1=31,h取值0,16,48,计算结果:
0&11111=0
10000&11111=10000(16)
110000&11111=10000(16)
可以看出n扩大一倍后,0,16,48的计算结果分为两组值0,16(对应变化前n值);对应在HashMap中,就需要将原Map的角标0位置的链表分割成两组,第一组的角标为0,第二组角标为16(0+16)
这次先到这儿,下次继续