HashMap源码(JDK1.8)深度分析-putVal

阅读本专栏需要了解HashMap底层数组+链表+红黑树的数据结构。

最近没大有时间暂时没空组织过多的文字描述和图例,后期我会补上,先直接上源码吧。

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;
		//判断table是否为空
        if ((tab = table) == null || (n = tab.length) == 0)
			//为空则调用resize方法扩容,并将table长度赋给n
            n = (tab = resize()).length;
		
		//检查当前根据hash值计算的当前元素位置是否为空
		//计算数组下标的的逻辑(n - 1) & hash,实际上是hash对(n-1)的求余
		//因为n是2的次幂(HashMap的初始化和扩容算法决定),所以n的2进制只有最高位是1
		//  假设hash值为 478,569,转换成2进制 0000 0000 0000 0111 0100 1101 0110 1001
		//           假设n=16,则n的2进制数为 0000 0000 0000 0000 0000 0000 0001 0000
		//                     n-1的2进制数为 0000 0000 0000 0000 0000 0000 0000 1111
		//  从二进制可以看出,任何数对2的次幂求余,实际上对于被除数从2的次幂为1的那位开始往上的高位都是可以忽略的,不管这些高位是0还是1,都不影响求余结果
		//  而且可以的出,求余的结果肯定就是除了这些高位之外的剩下的地位
		//  而对n-1进行与操作,相当于把n-1的2进制数作为一个掩码,进行与运算后,只保留了被除数地位的数据。
		//  因此说,对n(也就是2的次幂)求余等同于,对(n-1)做与操作。
        if ((p = tab[i = (n - 1) & hash]) == null)
			//为空直接创建一个节点放到该位置
            tab[i] = newNode(hash, key, value, null);
        else {
			//不为空的情况
            Node<K,V> e; K k;
			//先判断当前元素是否是是不是要查找的key
			//两个条件都要满足,一个是哈希值必须相同,另一个是Key必须相等(也可以重写key的equals方法,自定义相等的算法)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
				//找到后,取出当前元素p赋值给e
                e = p;
            else if (p instanceof TreeNode)
				//如果p是红黑树节点,则调用红黑树的put逻辑
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
				//else的情况说明此处为链表结构,此时通过for循环遍历整个链表,并计数
                for (int binCount = 0; ; ++binCount) {
					//找到链表的最后,人没有找到
                    if ((e = p.next) == null) {
						//则创建一个节点,挂在链表的最后
                        p.next = newNode(hash, key, value, null);
						//计数的目的在这,如果链表长度>=树化的临界点(TREEIFY_THRESHOLD,值为8)了,
						//则调用树化的方法(并不是一定会树化,还要看数组长度是否超过了64,具体逻辑在treeifyBin防范中)
						//TREEIFY_THRESHOLD得值为8,为什么是8?这是统计方法计算出来的,
						//树化是需要消耗性能的,且树化后,对于该节点上数据的增删都要更负责,但查询效率很高。
						//数据量少了,使用链表比较划算,数量超过8了,使用红黑树才有价值
						//TREEIFY_THRESHOLD - 1,为什么要-1,因为上面的if已经判断过头结点了,此处是在第二个节点开始遍历的
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
					//如果找到了某个相同的key,判断逻辑同上面的第一个if判断
					//则跳出循环,此时e即为找到的节点
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
					//两个if条件都不满则,则继续循环看下一个节点
                    p = e;
                }
            }
			//e!=null说明找到了,即存在该key
            if (e != null) { // existing mapping for key
				//取出该节点的value
                V oldValue = e.value;
				//如果onlyIfAbsent为false,或者oldValue为null,则直接赋值为新value
				//此时要注意,HashMap虽然支持value为null,但是如果传入的onlyIfAbsent为true
				//null还是会被替换,这样保证了外层判断返回值为null即为成功的有效性。
				//不管这个null是oldValue还是在程序最后直接return的null
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
				//空方法,供HashMap的子类实现,例如LinkedHashMap
                afterNodeAccess(e);
				//此处直接返回,因为节点个数无变化,不用执行线面的modCount自增,size自增和resize等方法。
                return oldValue;
            }
        }
		//modCount自增
		//modCount是迭代器遍历用的,线程不安全的集合都有这个属性
		//目的是在迭代器遍历集合期间,如果有其他线程更改了节点数量,则modCount的值会发生改变,
		//此时迭代过程中会抛出异常ConcurrentModificationException,这就是所谓fail-fast策略。
		//注意到 modCount 不是 volatile,原因是这个变量会被频繁改动,使用volatile的代价会很高。
		//而且fail-fast策略不是用来尝试修复这个异常的,而是抛出异常告诉用户,不应这样使用。
        ++modCount;
		//size自增,且判断是否超过扩容阈值
        if (++size > threshold)
			//超过了,则调用扩容方法
            resize();
		//空方法,供HashMap的子类实现,例如LinkedHashMap
        afterNodeInsertion(evict);
        return null;
    }

	//hash算法
    static final int hash(Object key) {
        int h;
		//不是简单的返回hashCode,而是将计算出来的hashCode,高16位和第16位做异或运算。
		//目的是减少哈希值的低位冲突,让高位参与进来,最终计算的哈希值体现高位的特征。
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值