Java-HashMap底层原理

注:本文基于JDK 1.8

1.什么是HashMap?

是基于哈希表的Map接口的实现,HashMap是一个根据Hash算法存储键值对的一组集合(无序存储)。
查询和删除速度非常快。键值都可以存储为null。
键如果重复,则覆盖上一次put的键值对。这一点和HashTable则不同,HashTable不允许null键值对和重复的键值对。
HashMap是线程不安全的,HashTable是线程安全的。

2. HashMap重要参数

capacity:(初始容量)默认为1<<4(16),最大容量1<<30(1073741824)。当初始化容量大于 MAXIMUM_CAPACITY(最大容量)时,默认使用最大容量。
loadFactor:(负载因子)默认为0.75。
threshold: 阈值=capacity*loadFactor   该参数用于扩容。
其中capacity和loadFactor两个参数就决定了HashMap的性能,如果为了性能,就不要把初始化容量设置的太高(或把负载因子设置的太低)。

3.HashMapput方法源码解析

当我们往HashMap,put一个值时。
在这里插入图片描述
可以看见集合的容量(capacity)为16,负载因子(loadFactor)为0.75。阈值(threshold)为12=16×0.75。当size(元素数量)= threshold时,就会触发扩容机制。扩容的容量是当前容量×2,最大容量为1<<30=1073741824。

这里我们解释一下为什么存储键值对时不是按照队列存储的。

HashMap在put一个键值对时,首先会对键也就是key进行Hash算法,然后得出一个Hash值。根据这个Hash值来决定这个键值对放在哪个位置。这也就解释了为什么HashMap在存储键值对时是无序的。

了解过Hash算法的小伙伴应该也知道,不同的明文根据Hash算法得出的密文有可能是一样的。这种现象称为哈希碰撞。那么就会出现一种情况,两个不一样的key算出来的Hash值是一样的,这就会造成HashMap在存储键值对时发生冲突。我们来看看HashMap内部是如何解决这种情况的。

public V put(K key, V value) {
		//调用putVal方法完成put
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    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)
            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是否存在,存在则直接赋值
            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);
                        //如果链表的长度为8,将链表转化为红黑树存储
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //判断该key是否存在,存在则直接赋值
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //e代表了存在相同key的Node节点对象。如果e不等于空,则把旧的值覆盖为新的值。返回旧值,不执行size++
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                	//重新赋值
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //集合修改次数
        ++modCount;
        //判断是否需要扩容(当前size(键值对数量)+1>阈值)
        if (++size > threshold)
        	//扩容方法
            resize();
        afterNodeInsertion(evict);//空操作
        return null;
    }

通过简单分析以上代码可以发现,在调用putVal方法之前,首先将key进行Hash计算。
在这里插入图片描述

在putVal方法中判断table是否执行了初始化操作。

在这里插入图片描述

然后计算出键值对存储的位置,如果为空,则直接进行赋值。

在这里插入图片描述

反之,如果不为空,则代表出现了哈希碰撞。通过Node节点中的next属性进行添加链表操作。并且在链表容量达到8时,将链表转化为红黑树存储。

在这里插入图片描述

如图(画的有点垃圾,勿喷。)

在这里插入图片描述

4.通过HashMap的构造函数来修改负载因子(loadFactor)所带来的影响

测试代码:
在这里插入图片描述
默认值
在这里插入图片描述
负载因子(loadFactor)为0.4
在这里插入图片描述

这里是以100万键值对进行测试。
相对的,loadFactor减少的同时,put花费的时间也增加了。个人认为,当数据量越大时,loadFactor越小并不能代表性能越快。性能快的主要原因就是HashMap容器中所生成的链表越少越好,也就是哈希碰撞越少越好。当然,如果造成大量的空间浪费,也是得不偿失的。

结束语

以上就是我的HashMap底层原理总结。欢迎各位大佬指点!有什么说的不对的地方也希望大家在下方评论区发言。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值