JAVA--HashMap实现类

HashMap实现类

HashMap默认的初始容积大小为16,加载因子默认0.75,threshold阈值为【容积*加载因子】HashMap采用的是链表法解决哈希冲突问题,同时引入红黑树可以避免单个链表长度过长的问题。

  1.默认8将单向链表转换为红黑树,注意这里还有一个条件默认64,只有集合中的结点数

    大于64时才可能进行树化处理。

  2. 默认6将红黑树退化成链表。

hash函数的涉及需要考虑简单高效和分布均匀两个方面,所以首先获取key对象的hashCode值,然后要将hash值的高位和低位进行与运算后,再针对数组长度进行求余。

HashMap线程不安全,进行多线程操作时可能会出现扩容时执行rehash操作的死循环问题、脏读导致数据丢失问题和size值不精确的问题。

put方法实现流程

public V put(K key, V value) { // 向hashmap集合中添加一个key/value对数据
        return putVal(hash(key), key, value, false, true);
        }

        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
                Node<K, V>[] tab;
                Node<K, V> p;
                int n, i;
                // 1、如果table为空或者长度为0,那么调用resize方法扩容数组.实际上resize方法兼容了两个职责,创建初始化数组或者容量不足时进行扩容处理
                if ((tab = table) == null || (n = tab.length) == 0)
                        n = (tab = resize()).length;
                // 2、计算插入数据存储到数组的对应索引值,如果数组为空则不存在hash冲突,则直接插入。这里的hash值时通过hash(key)获取的
                if ((p = tab[i = (n - 1) & hash]) == null)// key值对应的hash值获取在哈比表中存储的索引下标 hash%n
                        tab[i] = newNode(hash, key, value, null);
                else {// 如果桶上已经存储了数据
                        Node<K, V> e;
                        K k;
                        // 2.1、判断table[i]的元素是否与需要插入的key值一样,如果相同则世界使用新的value覆盖旧有的value
                        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 判断原则时调用key对象中的equals方法
                                e = p;
                        // 2.2、继续判断需要插入的数据结构是否为红黑树还是链表,如果红黑树则直接在树中注解插入或者更新键值对
                        else if (p instanceof TreeNode)
                                e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
                        else {
                                // 2.3、遍历table[i],判断key是否已经存在,采用equals对比当前遍历节点的key与需要插入的数据的key,如果相同则直接覆盖
                                // 2.4、遍历完毕后发现没有出现对应的key,则直接在链表尾部插入数据,插入完成后判断链表的长度是否大于8,如果是则进行树化处理,将链表转换为红黑树
                                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;// fail-fast检测
                if (++size > threshold) // 判断当前集合中的元素个数是否大于阈值,如果大于阈值则进行扩容处理
                        resize();
                afterNodeInsertion(evict);
                return null;
        }

hash方法putVal(hash(key), key, value, false, true)

static final int hash(Object key) {
	int h;
	//当key为null,则直接返回hash值为0
	//当key不为null时,首先调用key中的hashCode方法获取key的hash值,然后将hashCode值向左移动16位,然后进行异或计算。【将高位的hashCode值和低位的hashCode值进行异或计算】
	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

将key的hashCode值的高16位和低16位进行异或计算,这是因为有些数据计算出的哈希值差异在于高位,但是HashMap里的哈希寻址【求余操作】时忽略容量以上的高位值,高低位异或扰动计算的目的是避免类似情况下的哈希碰撞。

resize()方法的说明:将table大小初始化或加倍。如果为null,则按照默认值初始数组(16、0.75)。否则,使用2倍扩容处理,因为每个容器中的元素必须保持在相同的索引中,或者移动在新表中具有二次方偏移。

涉及的参数

hashMap容量、负载因子和树化操作

预先设置的数组长度需要满足大于【预先估算的元素数量/负载因子】,同时还必须是2的幂数。

1.如果没有特殊要求不要更改参数,因为JDK自带的默认负载因子是适用于通用场景需求的。

 2.如果确实需要修改,建议不要超过0.75,因为过大的负载因子值会显著增加冲突,降低

   hashmap性能。

 3.如果使用太小的负载因子,可能会导致频繁的扩容,增加性能开销,本身访问性能会受

   到影响。

putVal中有2次resize操作,分别是第一次初始化时扩容或者数组的实际大小大于临界值。扩容时会伴随桶上元素的重新分发。jdk1.8是根据同一个桶的位置中进行判断(e.hash & oldCap)是否为0,如果不为0则移动带新位置【原始位置+增加的数组大小】

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值