HashMap的put方法源码(jdk1.8)

image.png

HashMap是底层结构是数组+链表,数组是Node(Entry)数组,链表是Node组成的链表,充分利用了Node的特性。链表的头结点存放在数组中。

put方法的大致思路

  1. 调用hash(key)方法,根据key对象的hashCode计算出kay的hash,根据hash计算出其在tab数组中的index,将键和值放入Node(Entry)中;
  2. 如果index位置的Node为空,称为没碰撞,直接Node放入数组中;
  3. 如果碰撞(index位置存在Node),如果key已经存在,替换old value(先跟头结点比较,key不相等时,循环链表比较),否则将Node链接到链表最后。
  4. 如果碰撞导致链表的长度大于等于7,将链表的结构转换为红黑树。
  5. 如果bucket存放的current capacity(当前容量)超过容量的load factor(0.75),就resize,容量扩大为两倍。

源码

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

static final int hash(Object key) {
    int h;
    //使用16位无符号右位移(>>>)异或(^)混合生成一个hash,替代key的hashCode,hash混合hashCode的高位和低位,以此来加大低位的随机性,用于减少碰撞。
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

/**
 * @param onlyIfAbsent 如果为true,不改变已经存在的value
 * @param evict 如果为false, 该表处于创建模式。
 * @return 先前的值, 或者为null如果没有
 */    
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
        Node<K,V>[] tab; //存放链表头结点的数组,数组的每个元素相当于一个桶,好像这个链表就存放于一个元素的空间中
        Node<K,V> p; //存放于数组i处的(头)节点
        int n;//数组tab的长度
        int i; //数组tab的下标值
        //刚开始table是null或空的时候,调用resize()方法,初始化一个长度为16的tab
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //用(n - 1) & hash计算出插入点在数组的下标。如果插入点为null,将此节点存放于此。(&为长运算符,使用在计算boolean表达式时,会强制计算&两边的算式。此处用作位运算,即将n-1及hash均转为二进制数,相加,同为1则结果为1,否则为0,结果始终在[0,n-1])
        //(第2个key跟第1个key相同时,必定hash值相同,index也相同)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);//键值对的next为空
        //否则就会发生碰撞
        else {
            Node<K,V> e; //相当于一个temp,一个暂时存放键值对的节点
            K k; //一个temp,暂时存放key的值
            //跟数组头结点p比较,如果key的hash值相等,key对象也相等。为什么要两者都满足,因为根据不同key对象的hashCode计算出来的hash可能相等,所以还需要通过比较引用("==")或者比较对象("equals")的方式判断。
            //你可能要说那可以直接比较key对象就行,因为key相同,hash肯定相同。我们根据hash不同(p.hash == hash为false),可以判断出不是同一个key,我们知道符号"&&"有短路功能,所以整体为false,不用每次都去比较key,提高了效率。
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;//我们将数组的头结点p赋给e,用于更改头结点的value
            //我们可以从下一个else中知道,一个桶只能存放8个节点,大于八个将转成红黑树存储。根据桶中的Entry数,判断p的类型是否是TreeNode类的实例
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//如果是,使用putTreeVal方法插入Entry
            //碰撞后,满足与桶中的第一个entry不等,且此时桶中Entry小于等于8个
            else {
                for (int binCount = 0; ; ++binCount) {//没有条件,通过break跳出循环
                    if ((e = p.next) == null) {//当p后没有节点时满足条件,此时桶中就一个Entry;或者此时p为桶中最后一个Entry
                        p.next = newNode(hash, key, value, null);//新建Entry链接上,此时e为null
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//当桶中存放第8个元素时,将链表转换成红黑树。
                            treeifyBin(tab, hash);
                        break;
                    }
                    //上面的if判断是否跟桶中的第一个Entry相等,而这个if是依次跟桶中的Entry比较
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;//如果此时桶中有多个Entry,执行完两个if后还没跳出循环,e=p.next,相当于p=p.next.继续循环.这个else最重要的一点是要理解---利用e,依次比较桶中的Entry.
                }
            }
            if (e != null) { // existing mapping for key//e不等于null的条件是桶中存在相同的Entry提前跳出循环
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)//onlyIfAbsent默认为false,oldValue可以为null
                    e.value = value;//替换value
                afterNodeAccess(e);//LinkedHashMap继承HashMap,此方法在LinkedHashMap中被重写.在这里没什么用,但是在LinkedHashMap中此处调用此方法移动节点到最后.
                return oldValue;//存在相同Entry时返回oldValue.我原来使用put方法时没想到还有返回值,所以还是要看看源码
            }
        }
        //HashMap继承Serializable,当HashMap被序列化时,transient变量不会被序列化
        ++modCount;//(当前元素的格式)modCout是transient变量
        //size也是transient变量,指Map中包含的键值对的数量。threshold默认为16*0.75
        if (++size > threshold)//如果数组中的Entry大于等于length*0.75
            resize();//调用resize方法将tab数组扩大为两倍
        afterNodeInsertion(evict);//LinkedHashMap继承HashMap,此方法在LinkedHashMap中被重写.
        return null;//默认返回空
}

测试代码

public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        for (int i = 0; i <= 20; i++) {
            String key = "key" + i;
            String value = "value" + i;
            map.put(key, value);
        }
        for (int i = 10; i <= 20; i++) {
            String key = "key" + i;
            String value = "value" + i + 1;
            map.put(key, value);
        }

        for (int i = 0; i <= 20; i++) {
            String key = "key" + i + 1;
            String value = "value" + i + 1;
            map.put(key, value);
        }
}

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容