java学习笔记(7)--HashMap源码解析(负载因子,树化策略,内部hash实现 resize策略)

HashMap源码解析(负载因子,树化策略,内部hash实现 resize策略)

讲在前面的小知识点:

哈希表(k,v):数组
根据相应的哈希算法计算key,返回值即为value存储的数组下标

哈希算法:f(k)->int即为v需要存储的数组下标

哈希冲突解决办法:
哈希算法计算的两个不同对象的哈希值相等的情况
eg:1%16 == 17%16
1、开放定址法:
寻找下一个为空的数组下标,而后将冲突元素存储
2、再散列法
再次使用一个不同的哈希算法再次计算一次
3、链地址法:HashMap使用此方法解决哈希冲突
将所有冲突元素按照链表存储

哈希表(k,v)

HashMap维护一个数组,每个数组中根据key值放入链表,链表达到一定长度后转化为红黑树。

== 内部属性:==
1> 负载因子:final float loadFactor(默认为0.75f)
2> 实际容量:int threshold = loadFactor*tab.length;
3> 树化阈值:int TREEIFY_THRESHOLD = 8;
4> 解除树化阈值: int UNTREEIY_THRESHOLD = 6;

这里通过源码就能很好的看出HashMap中的一些基本实现规则

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //第一次put时,将哈希表初始化
    //resize()1.完成哈希表初始化2.完成哈希表扩容
    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恰好与当前元素key值相等且不为null
        //将value替换为当前元素的value
        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;
}

从源码可以得出以下几点:

1.HashMap也采用懒加载策略,第一次put时初始化哈希表;

2.树化逻辑:索引下标对应的链表长度达到阈值8并且当前哈希表长度达到64才会树化,否则只是调用resize方法进行哈希表扩容。

3.resize():扩容为原先数组的2倍
4.负载因子过大或导致哈希冲突明显增加,节省内存
负载因子过小会导致哈希表频繁扩容,内存利用率低

5.当链表长度过长时,会将哈希表查找的时间复杂度退化为o(n),树化保证即便在哈希冲突严重时,查找的时间复杂度也为o(logn)

6.当红黑树节点个数在扩容或删除元素时减少为6以下,在下次resize过程中会将红黑树退化为链表,节省空间

7.为何不直接使用哪个Object提供的hashCode
(h = key.hashCode())^(>>>16)
将哈希码保留一半,将高低位都参与哈希算法,减少内存开销,减少哈希冲突。

8.resize():1.完成哈希表初始化2.完成哈希表扩容

9.put内部逻辑:
1>哈希表索引下标计算:
i = (n -1 )&hash
问什么要做与运算:保证求出的索引下标都在哈希表的长度范围之内

2>.n:哈希表长度
n必须为2^n:保证Hash表中的所有索引下标都会被访问到
若n等于15,则以下位置元素永不可能存储元素
0011
0101
1001
1011
1111
15:0000 1111

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值