扒一扒HashMap的“底牌”

1. HashMap的几种构造方法

1.1 空参构造

空参构造解释:当我们进行实例化HashMap类,不进行参数传递的时候,HashMap底层就只是进行了加载因子的初始化

加载因子:就是对当前HashMap是否需要扩容起决定性因素的一个因子,例如:现在HashMap的初始化长度为16,加载因子为0.75,这个时候扩容门槛(threshold)就是16*0.75 = 12,意思就是当HashMap中的节点(node)数量超过了12(不等于),将会触发扩容事件进行HashMap扩容。

1.2 带初始化HashMap长度的构造方法

带初始化HashMap长度的构造函数解释:当你进行实例化HashMap的时候,如果传递了一个初始化长度的参数,HashMap底层就是调用了下面我需要叙述的另外一个构造方法

1.3 带初始化HashMap长度和加载因子的构造方法

带初始化HashMap长度和加载因子的构造方法解释:

  • 首先,判断当前传递的初始化长度时候小于0,小于0,则抛出非法参数异常
  • 第二,判断当前传递的容量是否大于HashMap的最大存储容量MAXIUM_CAPCITY,如果大于,则将初始化长度设置为最大长度
  • 第三,判断当前的加载因子是否小于等于0或加载因子是否为NaN,如果是,则抛出非法参数异常
  • 最后,传递参数都是合法的,将传递参数设置为HashMap的初始化配置值,其中涉及到的tableSizeFor方法,在这里暂时不赘述,后面我们进行叙述的时候会进行详细说明,现在你只要知道这个方法是针对你传递的容量参数进行优化,已达到HashMap的性能最佳即可

说明:MAXIUM_CAPCITY:默认值为1 >> 30,即2的30次方

1.4 带Map对象的构造方法

带Map对象的构造方法解释:如果初始化时传递了一个Map类型的对象,那么HashMap底层首先初始化加载因子为默认加载因子,然后调用putMapEntries进行批量加入元素,对于putMapEntries方法后面会有详细的说明

2. HashMap中的put方法

2.1 首先,跟进HashMap中的put方法,发现他调用了另外一个方法,叫做putVal

put

2.2 跟进putVal,重点来了!!!这里就实现了HashMap中的put的具体实现

    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;
    }

2.2.1 逐行解读源代码

2.2.1.1 putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
		// 初始化一个用来存放当前HashMap的临时变量
		// 为了避免后续操作多次从内存中取map而提高性能损耗
        Node<K,V>[] tab;
		// 也是一个中转变量,用来存放当前下标占用元素的地址值,后续会进行更详细的说明
		Node<K,V> p;
		// 初始化两个存放当前HashMap的长度和当前下标的变量
		int n, i;


		// 这里就是我上面所叙述的临时存放当前HashMap
		tab = table
		// 将当前HashMap的长度赋值给变量n,便于后续的使用
		n = tab.length
		// 判断当前HashMap是否为空或者HashMap的长度是否为空
        if (tab == null || n == 0) {
			// 扩容操作
			tab = resize()
            n = tab.length;
        }
		// 计算当前元素应该存入的位置
		i = (n - 1) & hash
		p = tab[i]
		// 如果存入位置没有元素,则直接赋值即可
        if (p == null)
            tab[i] = newNode(hash, key, value, null);
        else {
			// 如果有元素
            Node<K,V> e; 
			K k;
			// 判断当前存储的元素的hash值是否相等
			// 且key的引用是否相等或key值相等
			// 注意:b2和b3的判断是为了避免哈希碰撞的情况发生,
			// 例如:abc和acD的计算哈希值是一样的,这就出现了哈希碰撞
			// 本来abc这个key和acD这个key是不冲突,但是通过计算得出冲突了,我们就可以通过二次比较key值是否相等的方式进行解决
			k = p.key
			Boolean b1 = p.hash == hash
			Boolean b2 = k == key
			Boolean b3 = key != null && key.equals(k)
            if (b1 && (b2 || b3)) { 
				// 如果出现了hash值一样且key值一样,保存当前索引值
				e = p;
            }
			// 如果没出现,判断当前元素是否是一个红黑树
            else if (p instanceof TreeNode)
				// 如果是则进行红黑树操作
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
				// 如果没有出现上面的情况,则按链表处理
				// 循环遍历链表
                for (int binCount = 0; ; ++binCount) {
					// 将下一个值赋值给临时变量
					e = p.next
					// 如果下一个值为空,则直接接在这后面
                    if (e == null) {
                        p.next = newNode(hash, key, value, null);
						// 判断当前链表有没有达到红黑树化的门槛
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
							// 如果达到了,就进行红黑树化,然后瑞出循环
                            treeifyBin(tab, hash);
                        break;
                    }
					// ①  与上述同理
					k = p.key
					Boolean b4 = e.hash == hash
					Boolean b5 = k == key
					Boolean b6 = key != null && key.equals(k)
                    if (b3 && (b4 || b5){
						break;
                    }
                    p = e;
                }
            }
			// 这一个if判断其实可以接到①所在代码块中,其实就是在当发生了key值一样的时候,需不需要进行覆盖操作
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
				// 这里的onlyIfAbsent,就是我们在putVal中传递的参数
				// 当发生了key相等  且  onlyIfAbsent为false或当前存储的值为空时,进行覆盖操作
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
				// 然后返回覆盖前的值
                return oldValue;
            }
        }
		// 判断当前容量是否达到了扩容阈值
        if (++size > threshold)
            resize();
		// 没有值被覆盖,则返回null
        return null;
    }
2.2.1.2 resize扩容方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
		// 初始化一个用来存放当前HashMap的临时变量
		// 为了避免后续操作多次从内存中取map而提高性能损耗
        Node<K,V>[] tab;
		// 也是一个中转变量,用来存放当前下标占用元素的地址值,后续会进行更详细的说明
		Node<K,V> p;
		// 初始化两个存放当前HashMap的长度和当前下标的变量
		int n, i;


		// 这里就是我上面所叙述的临时存放当前HashMap
		tab = table
		// 将当前HashMap的长度赋值给变量n,便于后续的使用
		n = tab.length
		// 判断当前HashMap是否为空或者HashMap的长度是否为空
        if (tab == null || n == 0) {
			// 扩容操作
			tab = resize()
            n = tab.length;
        }
		// 计算当前元素应该存入的位置
		i = (n - 1) & hash
		p = tab[i]
		// 如果存入位置没有元素,则直接赋值即可
        if (p == null)
            tab[i] = newNode(hash, key, value, null);
        else {
			// 如果有元素
            Node<K,V> e; 
			K k;
			// 判断当前存储的元素的hash值是否相等
			// 且key的引用是否相等或key值相等
			// 注意:b2和b3的判断是为了避免哈希碰撞的情况发生,
			// 例如:abc和acD的计算哈希值是一样的,这就出现了哈希碰撞
			// 本来abc这个key和acD这个key是不冲突,但是通过计算得出冲突了,我们就可以通过二次比较key值是否相等的方式进行解决
			k = p.key
			Boolean b1 = p.hash == hash
			Boolean b2 = k == key
			Boolean b3 = key != null && key.equals(k)
            if (b1 && (b2 || b3)) { 
				// 如果出现了hash值一样且key值一样,保存当前索引值
				e = p;
            }
			// 如果没出现,判断当前元素是否是一个红黑树
            else if (p instanceof TreeNode)
				// 如果是则进行红黑树操作
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
				// 如果没有出现上面的情况,则按链表处理
				// 循环遍历链表
                for (int binCount = 0; ; ++binCount) {
					// 将下一个值赋值给临时变量
					e = p.next
					// 如果下一个值为空,则直接接在这后面
                    if (e == null) {
                        p.next = newNode(hash, key, value, null);
						// 判断当前链表有没有达到红黑树化的门槛
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
							// 如果达到了,就进行红黑树化,然后瑞出循环
                            treeifyBin(tab, hash);
                        break;
                    }
					// ①  与上述同理
					k = p.key
					Boolean b4 = e.hash == hash
					Boolean b5 = k == key
					Boolean b6 = key != null && key.equals(k)
                    if (b3 && (b4 || b5){
						break;
                    }
                    p = e;
                }
            }
			// 这一个if判断其实可以接到①所在代码块中,其实就是在当发生了key值一样的时候,需不需要进行覆盖操作
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
				// 这里的onlyIfAbsent,就是我们在putVal中传递的参数
				// 当发生了key相等  且  onlyIfAbsent为false或当前存储的值为空时,进行覆盖操作
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
				// 然后返回覆盖前的值
                return oldValue;
            }
        }
		// 判断当前容量是否达到了扩容阈值
        if (++size > threshold)
            resize();
		// 没有值被覆盖,则返回null
        return null;
    }

HashMap的源码与本文有差异,为了方便大家阅读,进行了一些修改和删除,如有需要,请自行寻找相关完整源码阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kkuil

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值