浅显易懂HashMap的数据结构

HashMap 就像一个大仓库,里面有很多小柜子(数组),每个小柜子可以挂一串链条(链表),链条太长的时候会变成更高级的架子(红黑树)。下面用超简单的例子解释:


1. 排列方式

  • 数组:一排固定的小柜子(比如柜子0、柜子1、柜子2...)。
  • 链表:如果多个东西要放在同一个柜子里,它们会串成一条链子。
  • 红黑树:当某个柜子的链子太长(比如超过8个),链子会变成一个小架子(树结构),找东西更快。

2. 存数据的过程

假设我们要存一个键值对 ("name", "小明")

  1. 找柜子:先算 "name" 的哈希值(类似身份证号),比如得到柜子3。
  2. 放数据
    • 如果柜子3是空的,直接放进去。
    • 如果柜子3已经有东西,检查链条上的每个元素:
      • 如果有相同的键(比如已经有 "name"),替换它的值。
      • 如果没有,把新键值对挂到链子末尾。
  3. 链条转架子:如果链子长度超过8,就把链子变成红黑树架子。

3. 取数据的过程

假设要取 "name" 对应的值:

  1. 找柜子:算 "name" 的哈希值,确定是柜子3。
  2. 找数据
    • 如果柜子3是链子,遍历链子找 "name"
    • 如果柜子3是架子(红黑树),用树的快速查找方法。

4. 代码简化版(Java)​

// 小柜子(数组)
Node[] table = new Node[16];

// 链表节点(如果链子太长,会变成 TreeNode)
class Node {
    String key;
    String value;
    Node next; // 下一个节点
}

// 红黑树节点(架子结构)
class TreeNode extends Node {
    TreeNode parent;
    TreeNode left;
    TreeNode right;
}

// 存数据示例
void put(String key, String value) {
    int hash = key.hashCode();
    int index = hash % table.length; // 计算柜子位置
    
    if (table[index] == null) {
        // 柜子是空的,直接放进去
        table[index] = new Node(key, value);
    } else {
        // 柜子非空,挂到链子末尾或更新值
        // 如果链子太长,转成红黑树
    }
}

5. 一句话总结

HashMap​ 的数组是主干,链表解决哈希冲突,红黑树解决链表过长时的性能问题!

6. 源码:HashMap的putVal()方法


    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        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;
            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;
    }

7、我们来拆解 ​​“哈希冲突时,链表如何挂到数组上”​​ 的详细过程,用大白话 + 例子解释:


1. 核心原理:用链表串联冲突的键值对

  • 当两个不同的键(比如 "name" 和 "age")通过哈希计算后,分配到 ​同一个数组位置(同一个柜子)​​ 时,就会发生 ​哈希冲突
  • 此时,HashMap 会用 ​链表​ 把这些冲突的键值对串起来,挂在数组的这个位置上(类似挂一串钥匙)。

2. 具体挂链表的步骤(逐行分析)​

假设数组的某个位置 index 已经有数据,现在要存入新的键值对 (key2, value2)

  1. 检查是否重复:遍历链表,对比每个节点的 key

    • 如果发现某个节点的 key 和 key2 ​相同​(用 equals() 判断),直接覆盖它的值。
    • 如果链表中没有相同的 key,则把新节点 ​挂到链表末尾​(Java 8 之后是尾插法)。
  2. 链表挂载示意图

    // 数组的某个位置 index 已经有一个节点 Node1
    table[index] = Node1(key1, value1) -> null;
    
    // 存入新节点 Node2(冲突)
    Node1.next = Node2(key2, value2); // 挂到链表尾部
    table[index] = Node1 -> Node2 -> null;
  3. 代码逻辑简化版

    Node existingNode = table[index]; // 获取数组当前位置的链表头
    
    // 遍历链表,检查是否已有相同的 key
    while (existingNode != null) {
        if (existingNode.key.equals(newKey)) {
            existingNode.value = newValue; // 覆盖旧值
            return;
        }
        existingNode = existingNode.next;
    }
    
    // 如果没有重复 key,把新节点挂到链表末尾
    Node newNode = new Node(newKey, newValue);
    newNode.next = table[index]; // Java 8 之前是头插法(新节点变成链表头)
    table[index] = newNode;      // Java 8 之后是尾插法(直接挂在链表尾部)

3. 关键细节:头插法 vs 尾插法

  • Java 8 之前:新节点插入链表头部(头插法)。
    • 问题:多线程下可能导致链表成环(死循环)。
    • 示例:table[index] = 新节点 -> 旧节点 -> null
  • Java 8 之后:新节点插入链表尾部(尾插法)。
    • 改进:避免多线程下的链表成环问题。
    • 示例:旧节点 -> 新节点 -> null

4. 链表转红黑树的条件

  • 当链表长度 ​超过 8,且 ​数组总长度 ≥ 64​ 时,链表会转换成红黑树。
  • 如果数组长度 < 64,HashMap 会选择 ​扩容数组​(而不是转树),减少哈希冲突。

5. 完整流程示例

假设存入 ("name", "小明") 和 ("age", 18),它们的哈希值冲突(都映射到数组位置 3):

  1. 存入第一个节点

    table[3] = Node("name", "小明") -> null;
  2. 存入第二个节点(冲突)​

    // 检查链表,发现没有重复 key,挂到链表末尾
    table[3] = Node("name", "小明") -> Node("age", 18) -> null;
  3. 查找时:遍历链表,逐个对比 key,找到对应值。


​6.总结

  • 链表挂在数组上:通过节点的 next 指针串联冲突的键值对。
  • 插入位置:Java 8 之后用尾插法,避免多线程问题。
  • 转红黑树:链表过长时优化查找速度(从 O(n) 优化到 O(log n))。

8、再帮你用 ​​“仓库管理员” 的比喻​ 总结一下,确保彻底理解:


终极傻瓜版总结

  1. 仓库(数组)​:管理员有一排柜子(比如16个),每个柜子有编号(0到15)。
  2. 存东西(键值对)​
    • 管理员用 ​哈希函数​(比如 key.hashCode() % 16)算出东西应该放哪个柜子。
    • 如果柜子空,直接放进去。
  3. 柜子冲突(哈希冲突)​
    • 如果柜子已经有东西,管理员会拿一根 ​链条(链表)​​ 把新东西和旧东西绑在一起,挂在柜子里。
    • 链条的挂法:新东西挂在链条的尾部(Java 8之后)。
  4. 链条太长怎么办
    • 如果链条长度超过8,管理员会把链条换成 ​高级货架(红黑树)​,这样找东西更快。
    • 但如果仓库的柜子总数太少(比如小于64个),管理员会直接 ​扩建仓库(扩容数组)​,而不是换货架。

关键逻辑图示

// 数组(柜子列表)
Node[] table = [柜子0, 柜子1, ..., 柜子15];

// 柜子里的内容(链表或树)
柜子3 -> Node1("name", "小明") -> Node2("age", 18) -> null  // 链表形式
柜子5 -> TreeNode("id", 1001)  // 树形式(如果链表转成了红黑树)

容易混淆的点

  1. 覆盖值:如果两次存同一个 key(比如两次存 "name"),会直接覆盖旧值。
  2. 哈希函数hashCode() 决定柜子位置,但不同对象可能算出相同的哈希值(冲突)。
  3. 扩容:当仓库的柜子被填满超过一定比例(默认75%),管理员会扩建仓库(数组长度翻倍),重新分配所有东西的位置。

9、结合 : 面试被问 Java中hashmap数据结构 

        URL: 地基:Java中hashmap数据结构(面试被问)-CSDN博客

兄弟们,理解好了。rt.jar包里的hashmap源码的putval方法的方式,有厉害的同学可以学以致用哈!向大家致敬!

(望各位潘安、各位子健/各位彦祖、于晏不吝赐教!多多指正!🙏)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在zotero里部署papaerschat插件,以接入现有大模型

papaerschat插件里集成了openAI的GPT3.5、gpt-4o、gpt-mini大模型以及Claude3、Gemini、Deepseek等大模型。通过接入这些大模型可以辅助我们阅读论文。以部署方式如下&#xff1a; 1.下载zotero的插件市场&#xff0c;用以管理zotero里的插件。下载地址&#xff1a; https://…

Memory Programming ...Error: File does not exist: Max.hex

Memory Programming ... Error: File does not exist: Max.hex 原因 删了确定就可以了

渗透测试【seacms V9】

搭建seacms环境 我选择在虚拟机中用宝塔搭建环境 将在官网选择的下载下来的文件解压后拖入宝塔面板的文件中 创建网站 添加站点 搭建完成seacmsV9 找到一个报错口 代码分析 <?php set_time_limit(0); error_reporting(0); $verMsg V6.x UTF8; $s_lang utf-8; $dfDbn…

仅需三分钟,使用Vue3.x版本组件式风格实现一个消息提示组件!

一、前言 在日常的前端项目开发中&#xff0c;我们时常需要使用到“消息提示”&#xff08;以下简称“消息”&#xff09;这个组件来帮助我们更好的给予用户提示&#xff0c;例如常见的“登录成功”、“操作成功”、“服务器异常”等等提示。 尽管市面上已经有一些组件库提供了…

敏捷开发实践指南:从理论到落地的全面解析

敏捷工程&#xff1a;现代软件开发的变革与实践 近年来&#xff0c;软件工程领域经历了从传统瀑布模型到敏捷开发的深刻转变。这种转变不仅是技术方法的升级&#xff0c;更是团队协作、需求管理和交付模式的革新。本文将从敏捷开发的核心理念、主流方法、实践案例及未来趋势等…

期权帮|股指期货基差和价差有什么区别?

锦鲤三三每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 股指期货基差和价差有什么区别&#xff1f; 一、股指期货基差 股指期货基差是指股指期货价格与其对应的现货指数价格之间的差额。 股指期货基差计算公式&#xff1a;基差 现…

【论文解读】《C-Pack: Packed Resources For General Chinese Embeddings》

论文链接&#xff1a;https://arxiv.org/pdf/2309.07597 本论文旨在构建一套通用中文文本嵌入的完整资源包——C-Pack&#xff0c;解决当前中文文本嵌入研究中数据、模型、训练策略与评测基准缺失的问题。论文主要贡献体现在以下几个方面&#xff1a; 大规模训练数据&#xf…

ARM 处理器平台 eMMC Flash 存储磨损测试示例

By Toradex秦海 1). 简介 目前工业嵌入式 ARM 平台最常用的存储器件就是 eMMC Nand Flash 存储&#xff0c;而由于工业设备一般生命周期都比较长&#xff0c;eMMC 存储器件的磨损寿命对于整个设备来说至关重要&#xff0c;因此本文就基于 NXP i.MX8M Mini ARM 处理器平台演示…

html中的元素(2)

在用块级元素完成网页的组织和布局以后&#xff0c;要为其中的每一个小区块添加内容&#xff0c;就需要用到行内元素&#xff1a; 1.字体样式元素 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>HTML5 保留的文本格式元…

代码随想录二刷|动态规划12

dp动态规划 动态规划五步曲 动态规划数组的含义 dp[i] 递推公式 动态规划数组的初始化 确定遍历顺序 手动模拟验证 动态规划遇到问题要打印dp数组&#xff0c;看和模拟结果哪里不一样 一 基础问题 斐波那契数 题干 斐波那契数 &#xff08;通常用 F(n) 表示&#xf…

linux 系统 安装禅道教程

禅道&#xff08;ZenTao&#xff09;是一款开源的项目管理软件&#xff0c;特别适用于敏捷开发和团队协作。它集成了需求管理、任务管理、缺陷管理、版本管理、文档管理等功能&#xff0c;旨在帮助团队更高效地管理项目&#xff0c;提升工作协同和开发效率。 禅道的主要特点&a…

CineMaster: 用于电影文本到视频生成的 3D 感知且可控的框架。

CineMaster是一种 3D 感知且可控的文本到视频生成方法允许用户在 3D 空间中联合操纵物体和相机&#xff0c;以创作高质量的电影视频。 相关链接 论文&#xff1a;cinemaster-dev.github.io 论文介绍 CineMaster是一种用于 3D 感知和可控文本到视频生成的新型框架。目标是让用…

Linux红帽:RHCSA认证知识讲解(四)修改远程配置文件,取消root禁用,便于使用root身份远程

Linux红帽&#xff1a;RHCSA认证知识讲解&#xff08;四&#xff09;修改远程配置文件&#xff0c;取消root禁用&#xff0c;便于使用root身份远程 前言一、远程连接的用途和原因二、通过 ssh 远程登陆系统三、默认限制及解决方案&#xff08;一&#xff09;非常规方法一&#…

OpenEuler学习笔记(三十五):搭建代码托管服务器

以下是主流的代码托管软件分类及推荐&#xff0c;涵盖自托管和云端方案&#xff0c;您可根据团队规模、功能需求及资源情况选择&#xff1a; 一、自托管代码托管平台&#xff08;可私有部署&#xff09; 1. GitLab 简介: 功能全面的 DevOps 平台&#xff0c;支持代码托管、C…

Rk3568驱动开发_点亮led灯(手动挡)_5

1.MMU简介 完成虚拟空间到物理空间的映射 内存保护设立存储器的访问权限&#xff0c;设置虚拟存储空间的缓冲特性 stm32点灯可以直接操作寄存器&#xff0c;但是linux点灯不能直接访问寄存器&#xff0c;linux会使能mmu linux中操作的都是虚拟地址&#xff0c;要想访问物理地…

免费使用 DeepSeek API 教程及资源汇总

免费使用 DeepSeek API 教程及资源汇总 一、DeepSeek API 资源汇总1.1 火山引擎1.2 百度千帆1.3 阿里百炼1.4 腾讯云 二、其他平台2.1 华为云2.2 硅基流动 三、总结 DeepSeek-R1 作为 2025 年初发布的推理大模型&#xff0c;凭借其卓越的逻辑推理能力和成本优势&#xff0c;迅速…

QML Text部件的使用

一个简单的Text代码 Text {id: txttext: qsTr("文本123abc\n数量的")color: "blue" } 效果&#xff1a; Text一般用于显示文本&#xff0c;例如可以给Button或者Rectangle等部件提供文本的显示&#xff1b; 1.文本常用 contentWidth 文本的宽度…

《Android-RecyclerView实现封面滑动到指定位置放大》---ViewPager封面指示器

一、实现效果 二、关键代码 1、自定义:LinearLayoutManager 指定位置放大item import android.content.Context; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup;import androidx.recyclerview.widget.LinearLayoutManager;…

【Bug】natten:安装报错(临近注意力机制的高效cuda内核实现)

正常安装natten报错 pip install natten 报错 可以尝试使用以下网站进行安装 https://shi-labs.com/natten/ 可以根据自己的cuda与pytorch版本进行安装 之间复制命令即可&#xff0c;不需要进行任何修改

智能合约安全 | 合约无效化攻击

目录&#xff1a; 智能合约安全 合约无效化攻击 合约自毁函数 selfdestruct 攻击实现 漏洞防御 总结 智能合约安全 合约无效化攻击 合约无效化攻击类同于web安全中的逻辑漏洞中的一种 我们这里拿一个典型的例子来讲解 有这样一份智能合约, 每个人可以向其中发送1 eth 第七个…