jdk1.8 HashMap扩容机制变化「建议收藏」

jdk1.8 HashMap扩容机制变化「建议收藏」概述JDK1.8中的HashMap较于前代有了较大的变更,主要变化在于扩容机制的改变。在JDK1.7及之前HashMap在扩容进行数组拷贝的时候采用的是头插法,因此会造成并发情景下形成环状链表造成死循环的问题。JDK1.8中改用了尾插法进行数组拷贝,修复了这个问题。其次,JDK1.8开始HashMap改用数组+链表/红黑树组合的数据结构来提高查询效率,降低哈希冲突产生的链表过长导致的查询效率减缓现象。本文的主要内容是对JDK1.8中的扩容机制与前代进行比较。JDK1.8之前的扩容由res

大家好,又见面了,我是你们的朋友全栈君。

概述

JDK1.8中的HashMap较于前代有了较大的变更,主要变化在于扩容机制的改变。在JDK1.7及之前HashMap在扩容进行数组拷贝的时候采用的是头插法,因此会造成并发情景下形成环状链表造成死循环的问题。JDK1.8中改用了尾插法进行数组拷贝,修复了这个问题。

其次,JDK1.8开始HashMap改用数组+链表/红黑树组合的数据结构来提高查询效率,降低哈希冲突产生的链表过长导致的查询效率减缓现象。

本文的主要内容是对JDK1.8中的扩容机制与前代进行比较。

JDK1.8之前的扩容

由resize()和transfer()两个方法共同完成。

 /**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
		// 新建数组
        Entry[] newTable = new Entry[newCapacity];
        // 拷贝原数据
        transfer(newTable);
        // 得到新数组
        table = newTable;
        // 更改阈值
        threshold = (int)(newCapacity * loadFactor);
    }
    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            // 获取当前node节点 e
            Entry<K,V> e = src[j];
            if (e != null) {
                // 将原数组该位置置空
                src[j] = null;
                do {
                    // 获取下一元素
                    Entry<K,V> next = e.next;
                    // 计算元素在新数组中的索引
                    int i = indexFor(e.hash, newCapacity);
                    // 头插法 插入新数组中
                    e.next = newTable[i];
                    // 在新索引位置放入元素
                    newTable[i] = e;
                    // e 指向下一元素
                    e = next;
                } while (e != null); //直至e为空,即全部复制完毕
            }
        }
    }

线程不安全问题

拷贝原数据采用的是头插法,在并发场景下,如果两个值在新数组中哈希冲突一样会出现环状链表的情形,最终导致死循环。如下图所示:

jdk1.8 HashMap扩容机制变化「建议收藏」

JDK1.8中的扩容

JDK1.8中将transfer()方法的操作也放入了resize()方法中,而由于JDK1.8引入了红黑树的结构,扩容的操作看起来也更加复杂。

 /**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
    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;
            }
            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) {
            // 数据转移操作
            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;
    }

这里对 if ((e.hash & oldCap) == 0) 进行一个解释说明

  • 这个结果等于0时,则将该头节点放到新数组时的索引位置等于其在旧数组时的索引位置,记为低位区链表lo开头-low;
  • 不等于0时,则将该头节点放到新数组时的索引位置等于其在旧数组时的索引位置再加上旧数组长度,记为高位区链表hi开头high.

具体推演过程见

(2条消息) HashMap扩容时的rehash方法中(e.hash & oldCap) == 0算法推导_Dylanu的博客-CSDN博客_e.hashicon-default.png?t=M3C8https://blog.csdn.net/u010425839/article/details/106620440

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/151075.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • eclipse 导入maven工程 在libraries 没有maven Dependencies「建议收藏」

    eclipse 导入maven工程 在libraries 没有maven Dependencies「建议收藏」导入maven工程的时候所有的依赖包下载不下来,最后而且在工程的buildPath====&gt;javaBuildPath====&gt;libraries中没有mavenDependencies,显示为org.eclipse.ide.MAVEN2_CLASSPATH_CONTAINER解决方法:1..classPath文件缺失&lt;classpathentrykind=…

  • 【控制】人工势场法及人工势场函数「建议收藏」

    【控制】人工势场法及人工势场函数「建议收藏」人工势场法是由Khatib提出的一种机器人路径规划算法。该算法将目标和障碍物分别看做对机器人有引力和斥力的物体,机器人沿引力与斥力的合力来进行运动。该法结构简单,便于低层的实时控制,在实时避障和平滑的轨迹控制方面,得到了广泛应用,其不足在于存在局部最优解,容易产生死锁现象,因而可能使移动机器人在到达目标点之前就停留在局部最优点。From:人工势场法1.概述我们打两个比方来说明人工势场法的作用机理。首先,我们把构型空间比作一个电势场平面,机器人(的当前构型)比作空间中一点。如果让机器人的起点和障碍物

  • (5)JMeter元件详解之Switch Controller

    (5)JMeter元件详解之Switch Controller

  • 反射型XSS的利用「建议收藏」

    反射型XSS的利用「建议收藏」反射型XSS:用户输入的恶意代码,被执行。利用:它通过给别人发送带有恶意脚本代码参数的URL,当URL地址被打开时,特有的恶意代码参数被HTML解析、执行。它的特点是非持久化,必须用户点击带有特定参数的链接才能引起。以前一直觉得反射型XSS危害不大,只能自己在客户端玩玩,实现弹窗。没想到,反射型XSS的利用比存储型还更容易,存储型XSS的利用还需结合CSRF.这篇博客很好的讲述了反射型XS…

  • django配置文件详解_pycharm配置python

    django配置文件详解_pycharm配置python前言django框架的日志通过python内置的logging模块实现的,既可以记录自定义的一些信息描述,也可以记录系统运行中的一些对象数据,还可以记录包括堆栈跟踪、错误代码之类的详细信息。log

  • Win10安装Tomcat服务器与配置环境变量[通俗易懂]

    Win10安装Tomcat服务器与配置环境变量[通俗易懂]文章目录下载安装JDK下载Tomcat压缩包解压Tomcat压缩包Tomcat目录结构启动Tomcat环境变量配置Tomcat服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP程序的首选。本文主要讲述Windows环境Tomcat服务器安装与环境变量配置下载安装JDK要想安装Tomcat服务…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号