美文网首页技术干货程序员
HashMap源码浅入深出(二)

HashMap源码浅入深出(二)

作者: 加薪猫 | 来源:发表于2017-09-08 18:02 被阅读0次

简书 加薪猫
转载请注明原创出处,谢谢!
这一系列主要介绍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()方法

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

相关文章

  • HashMap源码浅入深出(二)

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

  • HashMap源码浅入深出(一)

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

  • 深入浅出、深入深出、浅入浅出、浅入深出

    伊川思源实验学校 张文明 在网上读到这样一段话:世界上有四种老师,第一种是讲课能深入浅出,很深...

  • 世上有四种老师――顾明远

    1、深入浅出――轻负高效 2、深入深出――重负高效 3、浅入浅出――轻负低效 4、浅入深出――重负低效

  • 深入浅出

    文章有四种境界:深入浅出,深入深出,浅入浅出,浅入深出。深入浅出是最高境界,也最难。 没有对所论事物的深刻认识做不...

  • 论学习

    深入浅出、深入深出、浅入浅出、浅入深出。学而知之,学而习知,知之而不知,知之而学知。 孔子曰:“生而知之者,上也;...

  • 浅入深出Session

    在难也要学完《深入分析的Java Web》的深入理解Session与Cookie,断断续续花了三四天的时间在学习这...

  • HashMap剖析

    Java集合:HashMap源码剖析 一、HashMap概述 二、HashMap的数据结构 三、HashMap源码...

  • 最通俗易懂的 HashMap 源码分析解读

    HashMap 作为最常用的集合类之一,有必要深入浅出的了解一下。这篇文章会深入到 HashMap 源码,刨析它的...

  • Kubernetes HPA 浅入深出

    HPA 是 Kubernetes 提供了一个弹性伸缩的重要功能,简单来说就是支持在应用资源消耗量很大的情况下,根据...

网友评论

    本文标题:HashMap源码浅入深出(二)

    本文链接:https://www.haomeiwen.com/subject/jeoqjxtx.html