HashMap源码分析(二)
JDK1.8
从上一篇HashMap源码分析(一)我们可以看出上图的数据结构,但上次我就插入一个值,源码中没有分析到形成链表的机构,今天我们来分析一下怎么形成链表的。
先看测试代码:
HashMap hashMap = new HashMap();
hashMap.put("Aa","1");
hashMap.put("BB","1");//Aa,BB的hashCode是一样的,都是2112。
因为上一篇我们都分析过第一次put操作的流程来,我们就直接看第二次put:hashMap.put(“BB”,“1”);
我们就直接看putVal方法了,就不看hash方法来,反正知道**“Aa”和“BB”的hash()**返回值一样就行。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
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;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
因为已经调用一次put了,所以,table就是一个已初始化的数组了。并且通过**(n – 1) & hash=(16-1)&2112=0**也就是table[0] 存的一个Node对象(key=Aa,value=1);
这个时候p = tab[i = (n – 1) & hash]。然后看代码
//p.hash == hash 2112==2112 true
//k = Aa key = BB false
// p instanceof TreeNode false
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
所以直接走else代码快,同时p.next==null,所以就直接看代码:
p.next = newNode(hash, key, value, null);
//TREEIFY_THRESHOLD = 8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
newNode(hash, key, value, null);这个代码我们分析过就是初始化一个Node。然后if判断,binCount在第二次put操作的时候是0,注释也说的了 -1 for 1st,(-1代表第一个),if判断是false,先不管 treeifyBin(tab, hash);
然后到下面代码:
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
modCount是结构修改的次数,size是key-value结构的个数,都是2,不大于threshold(threshold在第一次put的时候就初始化了16*0.75=12.)“`
newThr= (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
threshold = newThr;
到这边,第二次put就结束了。
hashMap的内部数据结构也变成:
然后我们再讨论一下两次put的key一样的情况,看测试代码:
HashMap hashMap = new HashMap();
hashMap.put("Aa","1");
hashMap.put("Aa","1");
第二次的put方法前面都是一样的,就是在if判断的时候:
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
可以看到,在if判断为true的时候,把p复制给e,然后把e的value替换,然后返回。
我分析的是jdk1.8 的源码,在网上搜一下说1.7的和1.8不一样,在生成链表的时候不一样,特意看了一下,确实不一样。1.7是把新加入的节点作为头节点。代码如下:
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//将该节点作为头节点
table[bucketIndex] = new Entry<>(hash, key, value, e);
//数量加1
size++;
}