HashMap的put方法源码流程简析

HashMap是Java中使用最频繁的集合之一,同时也是面试中被高频提问的部分,其工作原理更是面试常见问题,这里针对put赋值的方法,这里做了一下简单的源码分析梳理流程,后面会写扩容,常量等源码分析的文章(ps:本文章的源码来自JDK1.8),本篇文章目录如下
在这里插入图片描述

一、源码简析

入口自然是耳熟能详的put(K key, V value)方法,源码如下:

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

这个没什么好说的,主要是调用putVal(hash(key), key, value, false, true),源码如下

// hash – hash for key key的hash计算结果
// key – the key key
// value – the value to put value值
// onlyIfAbsent – if true, don't change existing value 一个开关,用来判断是否覆盖原来的值
// evict – if false, the table is in creation mode.
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        // 变量n用来存放哈希桶的长度
        // 变量i用来记录新节点在哈希桶的索引位置
        // 变量p用来存放该索引位置上的节点值
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 预处理,判断哈希桶是否为空,如果为空,则创建一个一个默认参数的hash桶
        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;
            // 如果新节点的key等于哈希桶的的老节点,则将老节点赋值给临时变量e
            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) {
                    	// 如果没有,则生成一个新节点挂载在老节点,注意是这里临时变量e为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))))
                        // 如果下一个节点等于新节点,并结束本次循环,注意这里在上一步判断就已经将下一个节点的值赋给给临时变量e了
                        break;
                    // 。如果前面的条件都不满足,也即是有下一个点点,且该节点的值不等于新节点,则链表的遍历往下一级移动
                    p = e;
                }
            }
            // 如果临时变量e不为空,则意味着现有的Map中存在与新节点key相同的节点
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                // 如果允许覆盖,则直接覆盖原有节点的value值
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 如果有节点挂载的话,结构变化次数加1
        ++modCount;
        // 判断键值对总量是否大于罗荣的阈值,如果大于则需要扩容
        if (++size > threshold)
            resize();
        // 11、
        afterNodeInsertion(evict);
        return null;
    }

二、情形拆分

可能从第一个else开始,理解起来有点费解,这里我简单对链表的情况做了一下情形拆分,二三四的情形其实存在遍历的情况,也就是是说这个节点不满足这个情况的话,会对下一个节点做同样的情形判断
在这里插入图片描述

三、过程补充说明

这里需要对流程的一些具体做一下说明:
1、预处理
这个步骤会判断哈希桶是否为空,如果在声明一个HashMap时没有说明初始容量,就会执行if判断里的resize()方法,所以为了提高运行效率,尽量在声明HashMap时指定容量

// 默认构造方法没有创建哈希桶哟!!!
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

2、计算该节点在哈希桶中的位置
源码中采用的是位运算中的与运算(&),其逻辑意义是两个操作数的位都为1,结果才为1,否则为0。这个地方源码写的是 (n - 1) & hash,假如说容量为默认容量,那么n = 16,&左边即为15,用二进制表示即为11111,而&右边的hash值假设计算结果为1282225151,转为二进制为1001100011011010011001111111111,进行位运算的结果为11111,转为十进制即为15,而1282225151%16=15,所以这里的操作等同于用hash值对哈希桶的长度进行取模,得到的结果即为节点在哈希桶的索引位置

if ((p = tab[i = (n - 1) & hash]) == null)

3、临时变量p和临时变量e
临时变量p的意义主要是代表链表的节点node,比如开始时,将链表的头结点复制给p,后面也承担着遍历链表时代表的下一个节点;
临时变量e有两个作用:
第一是代表链表中等于key的老节点,如果说链表中不存在,则变量e在最后判断为空时,为会为空
第二是代表链表中的下一个节点

 if ((e = p.next) == null) 

4、链表转红黑树
源码中是用binCount(链表的节点个数)判断是否大于等于7,之所以是8-1而不是8,是因为这里包括了头节点,也就是在哈希桶的节点,换句话说,链表转红黑树的阈值8,是是包括头节点在内的节点书等于8

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    treeifyBin(tab, hash);

5、结构变化数累加和扩容
源码中我们可以看到,只有当新节点挂载的时候才会对modCount和size进行累加,同时判断累加后的size是否大于扩容的阈值,如果只是覆盖的话则不会进行这个操作

 ++modCount;
 if (++size > threshold)
    resize();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值