HashMap源码put方法JDK8

继承关系

在这里插入图片描述

重要的成员及内部类

Node类

static class Node<K,V> implements Map.Entry<K,V> {
	final int hash;
	final K key;
	V value;
	Node<K,V> next;

TreeNode类

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>  extends Node{
	TreeNode<K,V> parent;  //父节点
	TreeNode<K,V> left;	//左孩子
	TreeNode<K,V> right;  //右孩子
	TreeNode<K,V> prev;  //链表的前置节点
	boolean red; 		//红黑树标志

TreeNode是红黑树,也是一个链表
在这里插入图片描述

成员

//hash表
transient Node<K,V>[] table; 
//扩容阈值
int threshold;
//负加载因子
final float loadFactor;
//最小的 table[i] 树化 table的长度要求(table长度小于64之前 table[i] 元素个数一般不会超过8)
static final int MIN_TREEIFY_CAPACITY = 64;
//树蜕化成链表长度阈值
static final int UNTREEIFY_THRESHOLD = 6;
//table[i] 链表树化阈值
static final int TREEIFY_THRESHOLD = 8;

//默认负加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//new HashMap();默认table的长度
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//table长度达到MAXIMUM_CAPACITY之后直接Integer.MAX_VALUE
static final int MAXIMUM_CAPACITY = 1 << 30;

内部数据结构呈现

在这里插入图片描述

源码

put(K key, V value)

public V put(K key, V value) {
   return putVal(hash(key), key, value, false, true);
}
hash(key)扰动函数
//如果key是null一定放在table[0],因为任何数与0都为0
//否则返回key的hashcode 异或 hashcode无符号右移16位 
//这样做的目的让hash值区分度更高,让高16为参与低16运算
//原理:11111111 10101010 10101010 11111111
//异或:00000000 00000000 10101010 10101010
//     11111111 10101010 10101010 01010101
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

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

//hash:通过扰动后的值
//onlyIfAbsent:是否只有存在的时候替换
//evict:后置通知标志
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
	//tab:将要添加数据的数组;p:对应数组下标位置的头结点
    Node<K,V>[] tab; Node<K,V> p; 
    //n:数组的长度;i:对应数组的下标
    int n, i;
    //如果数组没有初始化或者数组长度为0,进行扩容
	if ((tab = table) == null || (n = tab.length) == 0)
		n = (tab = resize()).length;
	if ((p = tab[i = (n - 1) & hash]) == null)
 		//如果数组的i下标位置没有元素,直接添加node节点
		tab[i] = newNode(hash, key, value, null);
	else {
 		//数组i位置有数据,有数据要么是node链表,要么是红黑树
 		Node<K,V> e; K k;
 		//判断table[i]个元素是否和插入的元素一致
		if (p.hash == hash &&
			((k = p.key) == key || (key != null && key.equals(k))))
			//如果一致,把table[i]的元素赋值给e
			e = p;
		else if (p instanceof TreeNode)
			//如果table[i]节点属于红黑树,直接增加元素
			e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
		else {
			//其他是链表;咱们知道p就是table[i]的头节点
			for (int binCount = 0; ; ++binCount) {
   				//如果找到了table[i]对应链表的尾节点
				if ((e = p.next) == null) {
					p.next = newNode(hash, key, value, null);
     				//链表下标大于等于7之后,也就链表长度大于等于8,进行树化
					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
				p = e;
			}
		}
   		//存在相同的key和hash(更新)
		if (e != null) {
			V oldValue = e.value;
  			//如果需要替换或者原来的对象为null,用新的value替换旧的value值
			if (!onlyIfAbsent || oldValue == null)
				e.value = value;
 			//更新后置通知
			afterNodeAccess(e);
			return oldValue;
		}
	}
    //容器元素变化+1
	++modCount;
    //超过扩容阈值
	if (++size > threshold)
   		//扩容
		resize();
    //插入节点后置通知
	afterNodeInsertion(evict);
	return null;
}
treeifyBin(Node<K,V>[] tab, int hash)
//进行树化
final void treeifyBin(Node<K,V>[] tab, int hash) {
    //定义table的长度n,根据hash求出需要树化的table[j]位置index
    //定义遍历table[index] 链表的对象
    int n, index; Node<K,V> e;
    //如果table的长度小于64需要扩容分散
    //即使table[n]元素个数大于等于8,扩容后会进行切开分布,链表长度会小于等于8了
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        //不清楚这里为什么要做这个判断,后续会继续看下为什么这么做
        //如果只是为e赋值的话完全可以不要else if,应该是哪里存在table[index]为null的情况
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            //如果尾节点为null,把p赋值给头节点
            if (tl == null)
                hd = p;
            else {
                //还是熟悉的尾插法,双向链表
                //p的前一个节点为尾节点,
                p.prev = tl;
                //尾节点的下一个节点尾p
                tl.next = p;
            }
            //记录尾节点
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
           //真正树化的方法 
           hd.treeify(tab);
    }
}
resize() 扩容
//扩容
final Node<K,V>[] resize() {
    //得到扩容前的数组
    Node<K,V>[] oldTab = table;
    //得到扩容前数组的长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //获取到老的扩容阈值
    int oldThr = threshold;
    //定义新的数组容量,新的扩容阈值
    int newCap, newThr = 0;
    //老的数组容量大于0,有两种情况1、扩容过;2、创建Map指定了容量
    if (oldCap > 0) {
        //如果老的table容量大于等于最大值,放弃扩容,还是在老的table上增加元素
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //老数组长度*2 小于最大容量 并且 老的数组容量大于等于默认容量
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            //在老的扩容阈值上*2
            newThr = oldThr << 1;
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        //如果原数组的长度等于0 并且扩容阈值大于0,说明是通过new HashMap(n);创建,并且还是自一次添加元素
        //这样的条件下table的长度直接等于老的扩容阈值就可以了
        //一般指定table长度后 ++size不会大于threshold,不会触发扩容
        newCap = oldThr;
    else {
        //如果扩容阈值=0,只有一种情况new HashMap(); 并且第一次添加元素
        newCap = DEFAULT_INITIAL_CAPACITY;
        //默认0.75 * 16 = 12,也就是table中元素个数存储到13的时候就需要进行扩容 if (++size > threshold)
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //通过new HashMap(n)创建的对象,第一次添加元素,上面只赋值了table的容量
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        //重新计算扩容阈值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //到这里新的数组容量和扩容阈值已计算完成 newCap、newThr
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    //老数组已经扩容过,则需要把元素重新分布存储
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            //如果老数组中table[j]不为null,代表里面有元素
            if ((e = oldTab[j]) != null) {
                //清空引用,帮助GC
                oldTab[j] = null;
                //说明table[j]上只有一个元素;table[j]有四种情况,null、单个Node、Node链表、TreeNode红黑树
                if (e.next == null)
                    //重新计算元素在newTab中的位置(要么是j要么是j+oldCap位置)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    //重新分配红黑树
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else {
                    //链表元素划分
                    //声明低位的的头节点和尾节点
                    Node<K,V> loHead = null, loTail = null;
                    //声明高位的头节点和尾节点
                    Node<K,V> hiHead = null, hiTail = null;
                    //声明循环的下一个节点
                    Node<K,V> next;
                    do {
                        next = e.next;
                        //这里只有两种结果0 | oldCap
                        if ((e.hash & oldCap) == 0) {
                            //如果尾节点为null,代表未进行赋值
                            if (loTail == null)
                                //低位的头节点为e
                                loHead = e;
                            else
                                //如果已经赋值了,直接尾插法
                                loTail.next = e;
                            //记录尾节点
                            loTail = e;
                        }
                        else {
                            //高位和低位的原理是一样的
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        //我认为这里就是解决多线程的引用问题
                        loTail.next = null;
                        //低位的链表放在原来的链表位置
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        //高位的链表放在j+oldCap的位置
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) 切分红黑树
//切分红黑树
//map当前map对象
//tab:map中的数组
//index:oldtable下标,需要切分的TreeNode
//bit:oldtable长度
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    //得到当前Tree
    TreeNode<K,V> b = this;
    //定义切分后高低位存储的tree,和链表切分规则类似
    TreeNode<K,V> loHead = null, loTail = null;
    TreeNode<K,V> hiHead = null, hiTail = null;
    //定义高位的存储容量和低位的存储容量
    int lc = 0, hc = 0;
    for (TreeNode<K,V> e = b, next; e != null; e = next) {
        //从这里可以看出,TreeNode还维护了一个链表
        next = (TreeNode<K,V>)e.next;
        //解链
        e.next = null;
        //元素放在地位
        if ((e.hash & bit) == 0) {
            //从这里看出TreeNode不只是红黑树,还是一个双向链表
            //因为尾节点始终赋值,这里可以先判断尾节点,第一个元素是,头结点和尾节点一致,
            //这里不懂得可以看下LinkedList源码,底层也是这个逻辑
            if ((e.prev = loTail) == null)
                loHead = e;
            else
                //采用尾插法
                loTail.next = e;
            //把e赋值给尾节点
            loTail = e;
            //低位的元素个数+1
            ++lc;
        }
        else {
            //放在高位
            if ((e.prev = hiTail) == null)
                hiHead = e;
            else
                hiTail.next = e;
            hiTail = e;
            ++hc;
        }
    }

    if (loHead != null) {
        //如果低位得长度小于等于6,进行转化成链表
        if (lc <= UNTREEIFY_THRESHOLD)
            //低位链化后赋值在table[index]位值
            tab[index] = loHead.untreeify(map);
        else {
            //元素还是放在这个位置
            tab[index] = loHead;
            //不清楚这里为什么要满足高位必须要有元素 ???
            if (hiHead != null)
                //进行树化
                loHead.treeify(tab);
        }
    }
    if (hiHead != null) {
        if (hc <= UNTREEIFY_THRESHOLD)
            //高位链化后赋值在table[index + bit]位值
            tab[index + bit] = hiHead.untreeify(map);
        else {
            tab[index + bit] = hiHead;
            if (loHead != null)
                hiHead.treeify(tab);
        }
    }
}

treeify(Node<K,V>[] tab)

//把链表进行树化
final void treeify(Node<K,V>[] tab) {
    //定义红黑树根节点
    TreeNode<K,V> root = null;
    //this:需要树化的TreeNode双向链表,也是链表的头节点
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        next = (TreeNode<K,V>)x.next;
        //清空左右孩子节点引用
        x.left = x.right = null;
        if (root == null) {
            x.parent = null;
            //根节点为黑色节点
            x.red = false;
            root = x;
        }
        else {
            //获取key
            K k = x.key;
            //获取hash
            int h = x.hash;
            //定义K的比较器
            Class<?> kc = null;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph;
                K pk = p.key;
                //父节点的hash值如果大于当前需要插入节点的hash值
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    //compareComparables(kc, k, pk) == 0 有两种情况1、parent节点key为null;2、将要插入的key == parentkey
                    //进入到这个方法有两层1、无比较器2、当前key和parentkey大小一致
                    //此方法只为了解决k==pk的问题
                    //先根据key的className对比,如果还是0,再根据内存地址对比
                    //咱们知道相同的key一定是相同的hashcode,它们equals值也相同,map中这种情况是一个最多是个替换操作
                    dir = tieBreakOrder(k, pk);
                //上面这部分if else if 就是为了解决 k==pk = 0 的问题,并计算出 dir,dir大多情况是-1、1
                //定义x节点的父节点
                TreeNode<K,V> xp = p;
                //给p变量赋值,为了更好向子节点查找
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    //指定x这个节点的父节点
                    x.parent = xp;
                    if (dir <= 0)
                        //如果小于等于0,把x放在p的左节点
                        xp.left = x;
                    else
                        //否则放在右节点
                        xp.right = x;
                    //对插入节点后的树进行平衡操作
                    //这里需要了解下二叉树(二叉树、二叉搜索树、平衡二叉树 基础),有兴趣可以看下
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    moveRootToFront(tab, root);
}

PS:今天内容就到这里了,如果有帮助到您,请关注下博主点个赞吧~~~

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值