JDK8 HashMap扩容机制分析详解

 更新时间:2023年07月06日 11:13:45   作者:柯南是死神  
这篇文章主要为大家介绍了JDK8 HashMap扩容机制分析详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

HashMap

HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。可以存储null值,但是只有一个key可以为null,有多个值可以为null。

JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。

HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap 总是使用 2 的幂作为哈希表的大小。

put方法

HashMap只提供了put来添加元素,put内部调用putVal方法

对putVal 的分析如下

如果定位到的数组没有元素,就直接插入;

如果定位到的数组有元素,比较key,如果key相同就直接覆盖,如果相同,判断p是否是一个树节点

  • 如果是,调用树节点的插入方法将元素插入;
  • 如果不是,遍历链表,将元素插入到链表尾部;

说明:直接覆盖之后会return,不会有后续操作;链表长度大于阈值8并且数组长度大于64的时候才会转换红黑树,否则只是扩容数组。

put方法示例

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未初始化或者长度为0,进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //(n - 1) & hash 确定元素存放在哪个桶中,桶为空,
    //新生成结点放入桶中(此时,这个结点是放在数组中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    //桶中已经存在元素
    else {
        Node<K,V> e; K k;
        //key相等,hash相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            //将元素赋值给e
            e = p;
        //hash值不相等,即key不相等,并且该节点是红黑树节点
        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,执行treeifyBin方法
                    //此方法会根据HashMap的数组来决定是否要转换为红黑树
                    //数组长度大于等于64才会转换为红黑树,否则只会扩容数组
                    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;
            }
        }
        //桶中有key值和hash值于插入节点相等的节点
        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;
}

resize方法

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    //容量超过最大值就不再扩容
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //没超过最大值扩容到原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        //扩容完成后,重新进行hash分配,写入数据
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

以上就是JDK8 HashMap扩容机制分析详解的详细内容,更多关于JDK8 HashMap扩容机制的资料请关注脚本之家其它相关文章!

相关文章

  • Java代码审计的一些基础知识你知道吗

    Java代码审计的一些基础知识你知道吗

    这篇文章主要介绍了基于Java的代码审计功能的基础知识,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-09-09
  • spring配置不扫描service层的原因解答

    spring配置不扫描service层的原因解答

    这篇文章主要介绍了spring配置不扫描service层的原因解答,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 如何在JDK 9中更简洁使用 try-with-resources 语句

    如何在JDK 9中更简洁使用 try-with-resources 语句

    本文详细介绍了自 JDK 7 引入的 try-with-resources 语句的原理和用法,以及介绍了 JDK 9 对 try-with-resources 的改进,使得用户可以更加方便、简洁的使用 try-with-resources 语句。,需要的朋友可以参考下
    2019-06-06
  • 教你怎么用java实现客户端与服务器一问一答

    教你怎么用java实现客户端与服务器一问一答

    这篇文章主要介绍了教你怎么用java实现客户端与服务器一问一答,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • MyBatis3源码解析之如何获取数据源详解

    MyBatis3源码解析之如何获取数据源详解

    用myBatis3与spring整合的时候,我们可以通过多种方式获取数据源,下面这篇文章主要给大家介绍了关于MyBatis3源码解析之如何获取数据源的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • java 实现定时的方法及实例代码

    java 实现定时的方法及实例代码

    这篇文章主要介绍了java 定时任务详细介绍及实例代码的相关资料,需要的朋友可以参考下
    2017-03-03
  • 关于springboot响应式编程整合webFlux的问题

    关于springboot响应式编程整合webFlux的问题

    在springboot2.x版本中提供了webFlux依赖模块,该模块有两种模型实现:一种是基于功能性端点的方式,另一种是基于SpringMVC注解方式,今天通过本文给大家介绍springboot响应式编程整合webFlux的问题,感兴趣的朋友一起看看吧
    2022-01-01
  • 关于spring boot整合kafka+注解方式

    关于spring boot整合kafka+注解方式

    这篇文章主要介绍了关于spring boot整合kafka+注解方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java Elastic-Job分布式定时任务使用方法介绍

    Java Elastic-Job分布式定时任务使用方法介绍

    xxl-job 通过一个中心式的调度平台,调度多个执行器执行任务,调度中心通过 DB 锁保证集群分布式调度的一致性,这样扩展执行器会增大 DB 的压力,然而大部分公司的任务数,执行器并不多;xxl-job 提供了非常好用的监控页面甚至还有任务失败的邮件告警功能
    2023-01-01
  • springboot利用AOP完成日志统计的详细步骤

    springboot利用AOP完成日志统计的详细步骤

    项目用到了过滤器,可能有的人会不理解,之所以用过滤器是因为想要在日志记录post请求的json数据。本文重点给大家介绍springboot利用AOP完成日志统计的详细步骤,感兴趣的朋友跟随小编一起看看吧
    2021-12-12

最新评论