HashMap 正解

news2025/1/16 2:02:24

HashMap 实现原理 以及扩容机制

在这里插入图片描述

HashMap 的 put 以及扩容基本实现

数据结构

在这里插入图片描述

上述截图是 HashMap 的内部存储的数据结构。大体上是通过 hash 值来获取到对应的下标。如果当前下标为 null 的话,直接创建并设置一个新的节点,反之就是添加到该链表的最后

put 过程

  • 第一步:执行 put 方法 顺便获取数组的长度

    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    
    • 如果是第一次执行,table 就是 null,那么一定会执行到 if 判断中 然后执行扩容方法,此时变量n 就会有一个数组长度
  • 第二步:通过代码(n - 1) & hash 来计算出一个对应的下标,将对应下标的 tab 的值获取出来,判断是否为 null,如果是 null 的话,说明该节点没有任何值

    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    
  • 第三步:通过 hash 得到对应的下标,获取到下标对应的链表。

    • 循环判断 每个链表的节点的 hash 以及 key 是否相同,如果相同更新节点
    • 如果执行到最后还是没有找到,直接添加到链表最后。
  • 第四步:如果添加数组的 size > threshold 的话,调用方法resize() 进行扩容

扩容过程

  • 第一步:变量的初始化

    • 获取对应的 table 数组 oldTab. 如果是第一次的话 一定是一个 null
    • 获取对应的 table 数组的长度,如果oldTab === null 的话,设置为 0, 反之就是 oldTab 长度
  • 第二步:通过if (oldCap > 0) 来判断旧的数组中是否有值

    • 如果if (oldCap >= MAXIMUM_CAPACITY) 条件满足的话,说明长度已经是最大值了,无法扩容了。直接执行代码threshold = Integer.MAX_VALUE;, 返回旧的数组
    • 如果 newCap < 最大容量 && 旧数组长度 >= 默认的容量 满足的话,将新的newThr 扩大 旧阀值oldThr 一倍
  • 第三步:如果if (oldThr > 0) 满足的话,直接将老的阀值 赋值给 新数组长度

  • 第四步:上述以外的场合,赋值新的数组长度 以及新的阀值。

    • 新的数组长度是:newCap = DEFAULT_INITIAL_CAPACITY;
    • 新的阀值是(加载因子 * 默认长度):newThr = (int)(DEFAULT_LOAD_FACTOR _ DEFAULT_INITIAL_CAPACITY);
  • 第五步:如果满足newThr == 0的话,计算新的阀值。

    • 在这里插入图片描述
  • 第六步:此时计算出新的数组长度 以及阀值。 根据新数组长度构建新的数组,然后将旧数组的值 逐个移动到 新的数组上

HashMap 基本属性

 // 默认的初期化的容量
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
 // 最大的容量
 static final int MAXIMUM_CAPACITY = 1 << 30;
 // 加载因子
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 // 树的阈值
 static final int TREEIFY_THRESHOLD = 8;
 // 不可恢复的阈值
 static final int UNTREEIFY_THRESHOLD = 6;
 static final int MIN_TREEIFY_CAPACITY = 64;
 // 表示节点元素
 static class Node<K,V> implements Map.Entry<K,V>
 // 判断扩容的阀值 根据此值来判断是否进行扩容
 int threshold;

HashMap 构造方法

  • 多参构造方法
// hashMap 的多参构造方法
public HashMap(int initialCapacity, float loadFactor) {
    // 如果初始的容量长度 < 0的话 直接抛出异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    // 如果初始容量 > 最大的容量的时候 直接赋值最大容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    // 如果加载因子 <=0 或是 加载因子是NaN 的话 报异常
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    // 赋值加载因子
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}
  • 无参构造方法
// 表示无参构造方法
public HashMap() {
    // 默认的初始加载因子
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

HashMap Put 方法

// 添加元素的方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    // tab 整个存放hashMap的 tab
    // p 单个元素的Node节点
    Node<K,V>[] tab; Node<K,V> p; int n, i;

    // 此判断为table 为null || table数据为空 所以length == 0
    if ((tab = table) == null || (n = tab.length) == 0)
    	// 开始扩容 获取长度
        n = (tab = resize()).length;
    // 如果数组的指定位置(n - 1) & hash 为null的话 直接将新节点 赋值到这里
    // 通过公式(n - 1) & hash 根据hash 指定数组的位置
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 如果此时的节点为null的话  表示没有节点 直接插入新的节点
        tab[i] = newNode(hash, key, value, null);
    else {
    	// 如果可以执行到这一步的话 表示p不为null  获取的就是tab 指定位置的值
    	// e
        Node<K,V> e; K k;
        // 表示同一个hash戳  && key 保持一致
        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) {

            	// p 表示相同hash的节点 如果能执行到这里 表示p本身是一个链表节点
                if ((e = p.next) == null) {
                	// 构建下一个新的节点
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 一直循环下一个节点 查看是否可以更新 (hash相同 key相同)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }

        // 如果e 不为null 表示更新操作。
        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;
}

HashMap Resize 扩容方式

// 修改HashMap 的大小
final Node<K,V>[] resize() {
    // 旧的 table数组 就是存储数据的, 第一次肯定是null
    Node<K,V>[] oldTab = table;
    // 旧的tab oldTab == null ? 是0  : 反之就是oldTab 长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 阈值
    int oldThr = threshold;
    // 新的table  以及新的阀值
    int newCap, newThr = 0;
    // 如果旧的数组长度 > 0 说明旧table 中存在数据
    if (oldCap > 0) {
        // 旧数组长度 >= 最大值。 说明已经不能再次扩容了  设置阀值为最大值 然后返回旧table
        if (oldCap >= MAXIMUM_CAPACITY) {
            // 阀值设置为最大 说明已经没法扩容了 threshold
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // newCap 将旧数组长度 扩大 一倍
        // 如果newCap < 最大容量 && 旧数组长度 >= 默认的容量
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            // 把阀值扩大一倍
            newThr = oldThr << 1; // double threshold
    }
    // 如果旧的阀值 > 0 的话 直接使用旧的阀值 取代 新的数组
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        // 其余的走默认值
        // newCap 表示新的数组长度
        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"})
    // 表示一个新的tab
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    // 赋值table
    table = newTab;
    // 如果旧的table 不为 null
    if (oldTab != null) {
        // 通过for 循环oldCap 长度
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            // 如果不为null的话
            if ((e = oldTab[j]) != null) {
                // 将当前值设置为null
                oldTab[j] = null;
                // 如果e的next 是null
                if (e.next == null)
                    // 给newTab 赋值 e
                    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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/178548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

好客租房-09_学习MongoDB并完善通讯系统

9. 学习MongoDB 并完善租房的通讯系统后端本章目的为MongoDB快速入门, 并完善上一节编写的通讯系统后台, 将DAO层从HashMap迁移到MongoDB中.思考如下问题:MongoDB属于关系型还是非关系型数据库为什么在我们的通讯系统中选择MongoDB作为数据库?9.1 mongoDB概念简介MongoDB是一个…

python+django医院固定资产设备管理系统

管理员功能模块 管理员登录&#xff0c;通过填写用户名、密码、角色等信息&#xff0c;输入完成后选择登录即可进入医院设备管理系统&#xff0c; 管理员登录进入医院设备管理系统可以查看首页、个人中心、科室员管理、维修员管理、设备领用管理、设备信息管理、设备入库管理、…

人工智能入门杂记

本篇文章属于所有发表的文章的导读吧&#xff0c;以后会常更新。 目录 1.数据挖掘、机器学习、深度学习、云计算、人工智能 2.深度学习、强化学习、对抗学习、迁移学习 3.基础知识--线性代数 4.基础知识--概率与数理统计 5.常用工具库 6.机器学习 6.1 什么是训练什么是推…

Java数组

文章目录Java 数组一、数组介绍二、数组1. 数组静态初始化1.1 数组定义格式1.2 数组静态初始化2. 数组元素访问3. 数组遍历操作3.1 数组遍历介绍3.2 数组遍历场景3.3 数组遍历案例1&#xff09;数组遍历-求偶数和2&#xff09;数组遍历-求最大值3&#xff09;数组遍历综合案例4…

【C语言航路】第十四站:文件

目录 一、为什么使用文件 二、什么是文件 1.程序文件 2.数据文件 3.文件名 三、文件的打开和关闭 1.文件指针 2.文件的打开和关闭 四、文件的顺序读写 1.对于输入输出的理解 2.fgetc与fputc &#xff08;1&#xff09;fgetc与fputc的介绍 &#xff08;2&#xff0…

2023年springcloud面试题(第一部分)

1. 什么是微服务架构微服务架构就是将单体的应用程序分成多个应用程序&#xff0c;这多个应用程序就成为微服务&#xff0c;每个微服务运行在自己的进程中&#xff0c;并使用轻量级的机制通信。这些服务围绕业务能力来划分&#xff0c;并通过自动化部署机制来独立部署。这些服务…

MP-4可燃气体传感器介绍

MP-4可燃气体传感器简介MP-4可燃气体传感器采用多层厚膜制造工艺&#xff0c;在微型Al2O3陶瓷基片的两面分别制作加热器和金属氧化物半导体气敏层&#xff0c;封装在金属壳体内。当环境空气中有被检测气体存在时传感器电导率发生变化。该气体的浓度越高&#xff0c;传感器的电导…

JavaWeb | JDBC相关API详解 2 (内附以集合形式输出表)

本专栏主要是记录学习完JavaSE后学习JavaWeb部分的一些知识点总结以及遇到的一些问题等&#xff0c;如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 本专栏地址&#xff1a;&#x1f525;JDBC Java入门篇&#xff1a; &#x1f525;Java基础学习篇 Java进阶学习篇&#x…

C语言编程题

1、求斐波那契数列1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8……前20项之和 #include<stdio.h> int main() {int i,j,k,t2;ij1;printf("%d %d\n",i,j);for(k0;k<9;k){iij;jij;ttij;printf("%d %d\n",i,j);}printf(&q…

java七大查找 十大排序 贪心

七大查找 1.1二分查找(前提是 数据有序)说明&#xff1a;元素必须是有序的&#xff0c;从小到大&#xff0c;或者从大到小都是可以的。public static int binarySearc(int[] arr,int number){int min0;int maxarr.length-1;while(true){if(min>max){return -1;}int mid(maxm…

c++二插搜索树

1二插搜索树的概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: ​ 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 ​ 若它的右子树不为空&#xff0c;则右子树上所有节点的值都大于根节点的值 …

mongodb shell

连接指定数据库 .\mongosh.exe localhost:27017/test不连接数据库 .\mongosh.exe --nodb然后连接数据库 conn new Mongo("localhost:27017") /// mongodb://localhost:27017/?directConnectiontrue&serverSelectionTimeoutMS2000 db conn.getDB("test&q…

Git学习笔记(黑马)

目录 一、获取本地仓库 二、为常用指令配置别名 三、基础操作指令 四、分支 五、Git远程仓库&#xff08; 码云Gitee&#xff09; &#xff08;一&#xff09;配置SSH公钥 &#xff08;二&#xff09;Gitee设置账户公钥 六、操作远程仓库 &#xff08;一&#xff09;添…

【数据结构】详谈复杂度

目录 1.前言 2.什么是复杂度 3.如何计算时间复杂度 1.引例 2.二分查找 3.常见的复杂度 4.如何计算空间复杂度 5.关于递归 6.总结 1.前言 我们在做一些算法题时&#xff0c;经常会发现题目会对时间复杂度或者空间复杂度有所要求&#xff0c;如果你不知道什么是复杂度时&am…

SQL--DDL

目录 一、数据库的相关概念 二、MySQL数据库 1. 关系型数据库&#xff08;RDBMS&#xff09; 2. 数据数据库 3. MySQL客户端连接的两种方式 方式一&#xff1a;使用MySQL提供的客户端命令行工具 方式二&#xff1a;使用系统自带的命令行工具执行指令 三、SQL SQL的…

【C++】深浅拷贝

最近一些老铁一直问我深浅拷贝的问题&#xff0c;今天我们就来介绍一下深浅拷贝在说深浅拷贝构造之前&#xff0c;我们先介绍一下拷贝构造函数的应用场景&#xff1a;使用另一个同类型的对象来初始化新创建的对象。浅拷贝我们在学类和对象时了解到了类的6大默认函数&#xff0c…

给定一个数组arr,代表每个人的能力值。再给定一个非负数k,如果两个人能力差值正好为k,那么可以凑在一起比赛 一局比赛只有两个人,返回最多可以同时有多少场比赛

目录题目描述题目解析代码实现对数器题目描述 给定一个数组arr&#xff0c;代表每个人的能力值。再给定一个非负数k&#xff0c;如果两个人能力差值正好为k&#xff0c;那么可以凑在一起比赛一局比赛只有两个人&#xff0c;返回最多可以同时有多少场比赛 比如&#xff1a; [3&a…

MyBatis的入门

1、Mybatis的简介和特性 2、环境配置及其注意事项 2.1、注意事项 本文示例&#xff0c;开发环境 IDE&#xff1a;idea 2019.2 构建工具&#xff1a;maven 3.8.6 MySQL版本&#xff1a;MySQL 8 MyBatis版本&#xff1a;MyBatis 3.5.7 MySQL不同版本的注意事项&#xff1a;…

Allegro如何自动做差分对内等长操作指导

Allegro如何自动做差分对内等长操作指导 在做PCB设计的时候,需要给差分做对内等长,如果差分对比较多,Allegro支持自动做差分对内等长,如下图 具体操作如下 选择Route选择Auto-interactive Phase Tu

【UE4】将pmx导入到ue4中(obj-zip-mixamo绑骨)|模之屋模型导入UE4(较详细)

前言&#xff1a;我用fbx导入mixamo会报错&#xff0c;所以想用obj格式试试。 fbx导入↓ 效果预览&#xff1a; 目录 1.下载模型 2. 为blender安装插件 3.打开blender ​编辑 要删掉默认生成的方块&#xff01;&#xff01;&#xff01; 4.帮老婆找衣服环节&#xff01;&…