-HashMap实现类-

HashMap实现类

HashMap默认的初始容积大小为16,加载因子默认0.75,threshold阈值为【容积*加载因子】

HashMap采用的是链表法解决哈希冲突问题,同时引入红黑树可以避免单个链表长度过长的问题

  • 默认8将单向链表转换为红黑树,注意这里还有一个条件默认64,只有集合中的结点数大于64时才可能进行树化处理

  • 默认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的幂数

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

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

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

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

put总结

当put新元素时,首先计算key的hash值,这里会调用一个hash方法,hash方法时key.hashCode()与key.hashCode()>>>16进行高低位的异或计算,所以hash函数的作用是:高16位不变,低16位和高16位进行异或计算,从而尽量减少hash碰撞的概率

因为数组中元素bucket的个数是2的幂,计算元素存储的数组下标方法index=(table.length-1) & hash,如果不进行hash处理,则散列生效的值只有低几位的bit值,为了减少hash碰撞,所以使用高低位异或计算以减少碰撞。而且使用时间复杂度为O(logN)的红黑树结构提升碰撞下的性能

一般建议使用String或者Integer这样的包装类作为key

String、Integer之类的包装类特性能够保证hash值的不可更改型和计算准确性,能有有效地减少hash碰撞的概率

1、都是final类型并且具有不可变性,保证key值的不可更改,不会存在获取key值不同的问题

2、内部已经重写了equals和hashCode方法,遵守了HashMap内部规范,不容易出现hash计算错误问题

自定义对象作为key

必须重写hashCode和equals方法

  • 重写hashCode方法是因为需要计算存储数据的位置

  • 重写equals方法,需要遵守自反性、对称性、传递性、一致性

hashmap如何解决哈希冲突

1、使用链地址法来链接拥有相同hash值的数据

2、使用2次扰动来降低hash冲突的概率,使数据分布均匀

3、引入红黑树进一步降低遍历的时间复杂度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值