HashMap实现原理

1. 基本说明

  1. HashMap在jdk1.7为数组 + 链表结构,1.8后改为了数组 + 链表/红黑树

  2. new HashMap<>() 不指定容量时,默认为 16

  3. HashMap的大小为 2的幂,因为当数组大小为2的幂时,数组下标的计算公式 (n - 1) & hash 效果等价于 n % hash,且更高效,所以HashMap限制大小为2的幂的同时,选用了 (n - 1) & hash 作为计算公式

  4. 扩容因子默认为 0.75 ,容量变化后,会自动计算扩容阈值 threshold,数据插入完成后会判断 ++size > thresholdtrue 的话,即插入后容量大于阈值,执行扩容

  5. 红黑树长度 <= 6 时,红黑树转为链表;链表长度 >= 8 转化为红黑树

    // resize 扩容时触发
    if (loHead != null) {
        // UNTREEIFY_THRESHOLD = 6
        if (lc <= UNTREEIFY_THRESHOLD)
            tab[index] = loHead.untreeify(map);
        else {
            tab[index] = loHead;
            if (hiHead != null) // (else is already treeified)
                loHead.treeify(tab);
        }
    }
    if (hiHead != null) {
        if (hc <= UNTREEIFY_THRESHOLD)
            tab[index + bit] = hiHead.untreeify(map);
        else {
            tab[index + bit] = hiHead;
            if (loHead != null)
                hiHead.treeify(tab);
        }
    }
    
    // 数据插入
    for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
            p.next = newNode(hash, key, value, null);
            // 链表转红黑树判断 TREEIFY_THRESHOLD = 8
            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;
    }
    

2. 不同类型对比

HashMapHashTable(不推荐使用)ConcurrentHashMap
底层实现jdk1.7:数组+链表
jdk1.8:数组+链表/红黑树
数组+链表数组 + 链表 + 红黑树
线程安全线程不安全线程安全,大部分方法被synchronized修饰,执行时会锁住整个HashTable线程安全 jdk1.7:分段锁 jdk1.8:CAS+Synchronized,只锁定当前链表或红黑二叉树的首节点
效率对比
key限制最多只会有一个为null不允许为null不允许为null
value限制可以有多个为null不允许为null不允许为null
扩容默认容量16
newSize= oldSize * 2
默认容量11
newSize = oldSize * 2 + 1

3. 数据插入流程

这里以jdk1.8的来介绍,数据插入流程如下

  1. 判断hashMap的数组是否为空,为空进行初始化;
  2. 不为空,计算 key 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;
  3. 查看 table[index] 是否存在数据:
    • 没有:构造一个Node节点存放在 table[index] 中;
    • 有数据:在当前index下的结构(链表或红黑树),查找该key值,判断Key值是否存在相等
      • 相等:使用新value替换原数据,返回旧value(如果onlyIfAbsent 参数给 true 则啥都不做)
      • 不相等:执行插入流程
        1. 判断节点类型:
          • 树形节点:创造树型节点插入红黑树中
          • 链表节点:创建Node加入链表尾部,并判断是否 链表长度 > 8 且 数组长度 > 64,是的话该链表转为红黑树
        2. 插入完成,size是否大于扩容阈值 size > maxSize * 0.75,是的话扩容为原数组的二倍

4. JDK1.8的优化

  1. 结构优化,数据结构由数组+链表 改为了 数组+链表/红黑树
    1. 避免链表过长导致查询效率差,时间复杂度由O(n)降低为O(logn)
    2. 数据量较多或者hash散列性不够,都可能导致链表过长
    3. 红黑树的插入和查询效率处于完全平衡二叉树和链表之间
  2. 扩容优化:原本扩容会将key重新进行hash定位,后改为判断hashCode高位,高位为0则index不变,为1则index = index + 旧数组大小
  3. 链表的插入方式由头插法改成尾插法(新节点放在链表尾部):因为头插法会发生链表翻转,在多线程的情况下碰到扩容,存在产生环的风险
  4. 由先判断扩容再插入,改为插入完成后再判断扩容

PS:参考 https://blog.csdn.net/zhengwangzw/article/details/104889549 上面有详细的原理解释

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值