JAVA 中的 HashMap 工作原理

embedded/2025/3/22 8:41:56/

1. 底层数据结构

  • 数组 + 链表/红黑树‌:
    HashMap 内部维护一个 ‌桶数组(Node[] table)‌,每个桶(Bucket)存储链表或红黑树的头节点。
    java">transient Node<K,V>[] table; // 桶数组
    static class Node<K,V> implements Map.Entry<K,V> {final int hash;    // 键的哈希值final K key;V value;Node<K,V> next;    // 链表指针
    }
    
    • 链表‌:哈希冲突时,冲突的键值对以链表形式存储。
    • 红黑树‌(JDK 8+):当链表长度超过 ‌8‌ 时,链表转换为红黑树(优化查询效率);当节点数减少到 ‌6‌ 时,树退化为链表。

2. 哈希计算与索引定位

  • 哈希扰动‌:
    对键的 hashCode() 进行二次哈希(高16位异或低16位),以减少哈希碰撞概率。
    java">static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
  • 定位桶索引‌:
    根据哈希值计算桶的位置,公式为 hash & (table.length - 1)(要求 table.length 为 2 的幂)。

3. 插入键值对(put(K key, V value)

  1. 计算哈希值‌:调用 hash(key) 获取哈希值。
  2. 定位桶索引‌:通过哈希值确定键值对应存储的桶。
  3. 处理冲突‌:
    • 桶为空‌:直接创建新节点存入桶中。
    • 桶非空‌:遍历链表或树,检查是否存在相同键(equals() 判断):
      • 存在‌:更新值。
      • 不存在‌:插入新节点到链表/树末尾。
  4. 树化检查‌:若链表长度 ≥8,则转换为红黑树。
  5. 扩容检查‌:若元素总数 ≥ 容量 × 负载因子,触发扩容。

4. 查找值(get(Object key)

  1. 计算哈希值‌:同 put() 方法。
  2. 定位桶索引‌:找到对应桶。
  3. 遍历链表/树‌:通过 equals() 方法匹配键,返回对应值。若未找到,返回 null

5. 删除键值对(remove(Object key)

  1. 定位桶和节点‌:类似 get() 过程找到目标节点。
  2. 移除节点‌:若为链表节点,调整链表指针;若为树节点,调整树结构。
  3. 退化检查‌:若树节点数 ≤6,退化为链表。

6. 扩容机制(Rehashing)

  • 触发条件‌:当元素数量 ≥ 容量 × 负载因子(默认容量 16,负载因子 0.75)。
  • 扩容过程‌:
    1. 新容量为旧容量的 ‌2 倍‌(保证为 2 的幂)。
    2. 遍历所有节点,重新计算索引 hash & (newCapacity - 1)
    3. 数据迁移到新桶数组。
  • 优化设计‌:
    • 扩容后节点的新位置为 ‌原位置‌ 或 ‌原位置 + 旧容量‌(利用哈希值的高位特征)。
    • 避免全量重新哈希,提升效率。

7. 关键参数与性能

参数默认值作用
初始容量16桶数组的初始大小
负载因子(loadFactor)0.75触发扩容的阈值比例(空间与时间的权衡)
树化阈值(TREEIFY_THRESHOLD)8链表转换为红黑树的长度阈值
退化阈值(UNTREEIFY_THRESHOLD)6红黑树退化为链表的节点数阈值
  • 时间复杂度‌(平均情况):
    • 插入、删除、查找:‌O(1)
    • 最坏情况(全冲突):链表 O(n),红黑树 O(log n)。

8. 处理 null 键

  • HashMap 允许 ‌一个 null 键‌,其哈希值固定为 0,存储在索引 0 的桶中。
  • 查找时,需显式检查 key == null 的情况。

9. 线程安全问题

  • 非线程安全‌:多线程并发修改可能导致数据不一致或死循环(JDK 7 及之前扩容时链表反转导致)。
  • 解决方案‌:使用 ConcurrentHashMap 或 Collections.synchronizedMap()

10. 示例代码

java">HashMap<String, Integer> map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 5);// 查找
System.out.println(map.get("Apple")); // 输出 10// 删除
map.remove("Banana");

总结

HashMap 的高效性源于:

  1. 哈希算法优化‌:扰动函数减少哈希冲突。
  2. 动态扩容‌:平衡空间占用与操作效率。
  3. 冲突处理‌:链表与红黑树的灵活切换。
  4. 设计取舍‌:通过负载因子和树化阈值优化不同场景下的性能。

http://www.ppmy.cn/embedded/174643.html

相关文章

C++基础 [十二] - 继承与派生

目录 前言 什么是继承 继承的概念 继承的定义 基类与派生类对象的赋值转换 继承的作用域 派生类中的默认成员函数 默认成员函数的调用 构造函数与析构函数 拷贝构造 赋值运算符重载 显示成员函数的调用 构造函数 拷贝构造 赋值运算符重载 析构函数 继承与…

Spring Boot中接口数据字段为 Long 类型时,前端number精度丢失问题解决方案

Spring Boot中接口数据字段为 Long 类型时&#xff0c;前端number精度丢失问题解决方案 在Spring Boot中&#xff0c;当接口数据字段为 Long 类型时&#xff0c;返回页面的JSON中该字段通常会被序列化为数字类型。 例如&#xff0c;一个Java对象中有一个 Long 类型的属性 id …

蓝桥杯 第十天 :2022 国赛 第 2 题 排列距离/康托定理

实际上就是求字典序&#xff1a; 假设我们有 3 个数字&#xff1a;1, 2, 3。 排列组合总数: 3! 3 * 2 * 1 6 种。 这 6 种排列分别是&#xff1a; 1 2 31 3 22 1 32 3 13 1 23 2 1 康托展开: 对于排列 2 1 3&#xff0c;康托展开计算的结果是 2。这意味着 2 1 3 在所有 6 种…

JavaScript-函数、对象详解

一、函数 1.为什么需要函数&#xff1f; 作用&#xff1a;封装重复代码&#xff0c;实现复用示例&#xff1a;alert ()、prompt () 等内置函数 2.函数声明与调用 语法&#xff1a; function 函数名() {// 函数体 } 函数名(); // 调用 命名规范&#xff1a; 小驼峰命名&am…

windows+ragflow+deepseek实战之一excel表查询

ragflows平台部署参考文章 Win10系统Docker+DeepSeek+ragflow搭建本地知识库 ragflow通过python实现参考这篇文章 ragflow通过python实现 文章目录 背景效果1、准备数据2、创建知识库3、上传数据并解析4、新建聊天助理5、测试会话背景 前面已经基于Win10系统Docker+DeepSeek+…

Python实验:Python语言分支循环结构应用

[实验目的] 掌握分支结构&#xff0c;利用if语句实现单分支、双分支和多分支&#xff1b;掌握循环结构&#xff0c;运用while语句和for语句实现循环结构和循环嵌套&#xff1b;了解Python扩展库的使用方法&#xff1b;掌握程序的异常处理。 [实验和内容] 1.用户从键盘输入一…

家族族谱管理系统基于Spring Boot

目录 引言 一、系统概述 二、系统架构 三、功能模块 四、技术实现 五、系统特色 六、总结 引言 在数字化浪潮席卷全球的今天&#xff0c;家族文化的传承与延续面临着前所未有的挑战与机遇。传统纸质家谱因保存不便、查询困难、更新滞后等问题&#xff0c;已难以满足现代…

使用 Docker 构建 LangChain 开发环镜及 ChatOllama 示例

文章目录 Github官网简介Dockerfilerequirements.txt构建 LangChain 镜像ChatOllama 示例Ollama 示例模拟 tools Github https://github.com/langchain-ai/langchain 官网 https://python.langchain.com/docs/introduction/ 简介 LangChain 是一个用于构建 LLM 驱动的应用…