HashMap源码分析(二)

HashMap源码分析(二)

JDK1.8

《HashMap源码分析(二)》

从上一篇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);

《HashMap源码分析(二)》
这个时候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的内部数据结构也变成:
《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++;
}
    原文作者:Coder_Qiang
    原文地址: https://blog.csdn.net/Coder_Qiang/article/details/84318791
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞