HashMap源码浅入深出(二)

简书 加薪猫
转载请注明原创出处,谢谢!
这一系列主要介绍HashMap(1.8),记录也是分享,欢迎讨论

上回书说到get方法的爱恨情仇,其实主要还是简单介绍了HashMap的一些基本结构,还有HashMap的哈希值是怎么算出来的,这些可以为我们的put方法打下一定的基础,今天就来分享put方法

2.put(K,V)

老样子,先怼源码

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

put方法完结撒花,下面我们来讲putVal.(本猫一系列都将以JDK1.8为主讲述,如果有心人看源码时发现自己的源码可能是个假源码的请更换JDK版本,hash()方法可看上节)

putVal(int hash,K key,V value, boolean onlyIfAbsent,boolean evict)

先介绍这个方法的几个参数:
hash就是上节我们说过的,经过HashMap处理过的哈希值
key和value不谈,onlyIfAbsent这个参数的作用在于,如果我们传入的key已存在我们是否去替换,true:不替换,false:替换。
参数evict是用在LinkedHashMap的,这里暂且不谈,等之后分享LinkedHashMap的时候我们再来细细分析
我们先看putVal的代码

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;
    }

以上代码很长,加上手机上显示好像没有高亮,看着更是难受。所以读者可以以上面为机构,我会在下面拆分进项讲解。下面的拆分和上面的注解一起看,效果更加哦。

 Node<K,V>[] tab; Node<K,V> p; int n, i;

第一次看源码的时候,很容易搞混变量,尤其是看在中间的时候会突然走个神就想着,哎?我是谁,我在哪,我在干什么。。所以最好的方式,还是在看代码时,当你看懂这个变量的意义的时候就加上自己的注释。
tab:HashMap里面存储Node节点的数组
p:当前要put进HashMap的key,经过计算得出的tab下标所对应第一个Node。(啥?我在说啥? 第一个Node的意思是,我们的Node本来就是链表中的一个节点,我们根据Key只能找到这个链表的头) 这个更精确地说是当前处理的节点
n:tab当前的大小
i:key算出来的下标

        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

table是全局变量,先设置tab= table, n = tab.length,判断如果tab为null,或者tab的大小为0的话,我们就要调用resize()方法进行第一次扩容(resize()方法我们在下面详细说,这里你可以暂时理解为初始化HashMap),从这里我们可以知道的一点是HashMap不是在实例化话的时候就生成指定大小的Node<K,V>[],而是在第一次put时才会进行初始化。

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

这里是我们根据计算的哈希值(具体计算方式可以看上一节)算出下标的方式,(n-1)& hash,(15&3=3,15&18=2,与运算这里就不多说了哈~),如果这算出来的下标i,tab[i]为空的话,我们可以直接tab[i]下塞一个新的Node就好了。

④ 仿佛又是一大块,这也是这个函数重点理解的地方,⑤~⑪可以就看下面的代码,当然我上面也会标注的
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;
            }
        }

这一段主要讲的当算出的索引下标已经有值时HashMap的处理方式。

Node<K,V> e; K k;

e:如果要put进HashMap的key已存在,则e为这个key的节点,如果不存在则为null。
k:遍历链表时,当前节点的key

            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

总是判断第一个,如果算出的hash值相同并且key相同(这个key相同是指要么是同一个对象,要么是equals返回true),那么e=p然后直接进入⑪的判断

            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

如果p已经是TreeNode类了(也就是已经转换成红黑树了)就直接调用红黑树的putTreeVal,这个方法之后我们再具体讲,这里只要知道如果是key不存在这个方法返回null,如果存在返回对应的节点进入⑪的判断

for (int binCount = 0; ; ++binCount) {
.....
}

这个循环主要是用来记录链表的数量,之后如果超过默认值(8),会将链表转换为红黑树

if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

p.next==null的意思是指我们遍历链表已经找到最后一个节点了,那就是虽然算出下标下是有链表的,但是并没有相同key,所以直接创建新的节点放在链表的最后。同时,如果这个链表的长度已经大于默认值8就会将当前这个链表转换为红黑树,具体treeifyBin()方法我们之后分享红黑树的时候来分享。

if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;

如果要put的key已经被找到了,则直接进入⑪判断

if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

一般我们的put的方法都是会直接覆盖旧值的,其实一开始就想说的就是put方法是有返回值的!如果有旧值返回旧值,没有旧值就返回null。afterNodeAccess这个方法是LinkedHashMap用的,这里先不涉及

  ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;

首先代码能走到这里,是③为true或者是⑪为false,这两个是什么意思呢,意思是之前没有相同的key,也就是说HashMap的大小会+1,++modCount暂且不提,我们先看后面的,如果此时size+1后比threshold大(这个变量的值我们在讲resize的时候会重新介绍这里你可以简单以为是 容量*0.75),则会扩容。
afterNodeInsertion()方法也是LinkedHashMap的方法,这里暂不做介绍,至此Put方法完成啦~~

本猫的期望就是能够让大家更加容易的学习源码理解源码,诸君如果有建议在下面留言哦~下期会讲resize()方法

我是加薪猫
技术是加薪的前提,生活是加薪的意义
技术等于生活

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容