当前位置: 首页 > news >正文

HashMap 的底层原理

文章目录

    • 核心数据结构
    • 核心参数
    • 核心操作原理
      • 哈希计算与数组索引
      • 插入(put)操作
      • 查询(get)操作
      • 扩容(resize)操作
      • 链表转红黑树(treeifyBin)
      • 红黑树退化为链表(untreeify)
    • 关键特性
    • 总结

HashMap 的底层原理(Java 8 版本)

HashMap 是 Java 中最常用的基于哈希表的Map实现,它存储键值对(key-value),具有 O(1) 平均时间复杂度 的查询、插入和删除性能。其底层实现涉及 数组 + 链表 + 红黑树 的结构,并在 Java 8 中进行了优化(链表转红黑树)。下面详细分析其核心机制:


核心数据结构

HashMap 的底层是一个 Node<K,V>[] table 数组(默认大小 16),每个元素是一个链表或红黑树的节点:

transient Node<K,V>[] table; // 存储键值对的数组
  • Node 类(链表节点):

    static class Node<K,V> implements Map.Entry<K,V> {final int hash;      // key 的哈希值final K key;         // 键V value;             // 值Node<K,V> next;      // 下一个节点(链表结构)
    }
    
  • TreeNode 类(红黑树节点,Java 8 新增):

    • 当链表长度超过阈值(默认 8)时,链表会转换为红黑树,提升查询效率(从 O(n) → O(log n))。

核心参数

参数默认值作用
initialCapacity16初始数组大小(必须是 2 的幂次方)。
loadFactor0.75负载因子,决定扩容时机(容量 * loadFactor 触发扩容)。
threshold12扩容阈值 = 容量 * loadFactor(默认 16 * 0.75 = 12)。
TREEIFY_THRESHOLD8链表长度超过此值时,转换为红黑树。
UNTREEIFY_THRESHOLD6红黑树节点数减少到此值时,退化为链表。

核心操作原理

哈希计算与数组索引

  • 计算 key 的哈希值

    • 先调用key.hashCode(),再通过hash()方法扰动哈希值(减少哈希冲突):

      static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 高16位与低16位异或
      }
      
    • 扰动函数的作用:将高位哈希值参与低位运算,避免哈希冲突(如 key1.hashCode() 和 key2.hashCode()的高位不同但低位相同的情况)。

  • 计算数组索引

    • 使用(n - 1) & hash计算索引(n是数组长度,必须是 2 的幂次方):

      int index = (table.length - 1) & hash; // 等价于 index = hash % n(但位运算更快)
      
    • 为什么用 & 而不是 %?

      • n 是 2 的幂次方时,(n - 1) & hash 等价于 hash % n,但位运算效率更高。

插入(put)操作

  1. 计算 key 的哈希值和数组索引
  2. 检查数组是否为空或长度为 0:
    • 如果是,先调用 resize()扩容(默认初始容量 16)。
  3. 检查当前索引位置是否有节点:
    • 无节点:直接插入新 Node。
    • 有节点:
      • 如果 key 已存在:更新 value(equals() 比较 key)。
      • 如果 key 不存在:
        • 链表结构:插入到链表末尾。
        • 链表长度 ≥ 8:转换为红黑树(TREEIFY_THRESHOLD=8)。
  4. 检查是否需要扩容:
    • 如果当前元素数量 ≥ threshold,调用 resize()`扩容(容量翻倍)。

查询(get)操作

  1. 计算 key 的哈希值和数组索引
  2. 检查当前索引位置是否有节点:
    • 无节点:返回 null。
    • 有节点:
      • 链表结构:遍历链表,用 equals() 比较 key。
      • 红黑树结构:调用红黑树的查找方法(O(log n))。

扩容(resize)操作

  • 触发条件:元素数量 ≥ threshold(默认 16 * 0.75 = 12)。
  • 扩容过程:
    1. 新容量 = 旧容量 × 2(必须是 2 的幂次方)。
    2. 遍历旧数组的所有节点,重新计算哈希值和索引:
      • 旧容量为 2 的幂次方时,节点的新索引要么不变,要么是 旧索引 + 旧容量(利用哈希值的高位信息减少重新计算)。
    3. 如果链表长度 ≥ 8,转换为红黑树。

链表转红黑树(treeifyBin)

  • 条件:链表长度 ≥ TREEIFY_THRESHOLD=8 数组长度 ≥ MIN_TREEIFY_CAPACITY=64。
  • 作用:提升查询效率(从 O(n) → O(log n))。

红黑树退化为链表(untreeify)

  • 条件:红黑树节点数 ≤ UNTREEIFY_THRESHOLD=6。
  • 作用:减少内存占用(红黑树比链表占用更多空间)。

关键特性

  1. 哈希冲突处理:
    • 链地址法:相同哈希值的 key 存储在同一个链表或红黑树中。
    • 扰动函数:减少哈希冲突的概率。
  2. 扩容机制:
    • 容量翻倍,重新计算所有节点的索引(利用(n - 1) & hash的特性减少计算量)。
  3. 红黑树优化:
    • 链表过长时转换为红黑树,提升查询效率(Java 8 新增)。
  4. 线程不安全:
    • 多线程环境下可能导致死循环或数据丢失(需用 ConcurrentHashMap 或 Collections.synchronizedMap)。

总结

HashMap 的底层是基于数组 + 链表 + 红黑树的哈希表实现,其核心原理包括:

  1. 哈希计算:通过 key.hashCode() 和扰动函数计算哈希值,再用 (n - 1) & hash计算数组索引。
  2. 插入操作:先检查数组是否为空,再检查当前索引是否有节点,若无则插入;若有则更新或插入链表/红黑树。
  3. 扩容机制:当元素数量超过 容量 * loadFactor 时,容量翻倍并重新计算所有节点的索引。
  4. 链表转红黑树:当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转换为红黑树以提升查询效率。
  5. 线程不安全:多线程环境下需用 ConcurrentHashMap 替代。
    其设计目标是平衡查询效率、内存占用和扩容成本。

相关文章:

  • 【Vmware】虚拟机安装、镜像安装、Nat网络模式、本地VM8、ssh链接保姆篇(图文教程)
  • 0518蚂蚁暑期实习上机考试题3:小红的字符串构造
  • (17)课36:窗口函数的例题:例三登录时间与连续三天登录,例四球员的进球时刻连续进球。
  • 简单爬虫框架实现
  • Android Kotlin 算法详解:链表相关
  • Dify:启动 Web 服务的详细指南
  • Ubuntu 22.04 安装 Nacos 记录
  • ubuntu 20.04挂载固态硬盘
  • Windows下WSL(Ubuntu)安装1Panel
  • Ubuntu 挂载新盘
  • 飞腾D2000,麒麟系统V10,docker,ubuntu1804,小白入门喂饭级教程
  • seafile:ubuntu搭建社区版seafile12.0
  • 解决 Ubuntu 20.04 虚拟机中 catkin_make 编译卡死问题
  • CppCon 2014 学习:Anatomy of a Smart Pointer
  • windows安装和部署docker
  • 八.MySQL复合查询
  • LangChain操作指南
  • 手把手教你用Appsmith打造企业级低代码平台:从部署到性能调优实战
  • 化工厂爆炸事件看制造业AI转型
  • Manus AI与多语言手写识别的创新革命:从技术突破到行业赋能
  • 政府网站建设重点突出/还有哪些平台能免费营销产品
  • 虚拟服务器建网站/公司网站的作用
  • 西安 美院 网站建设/项目推广渠道有哪些
  • 淘宝客网站域名/怎么自己做网站
  • 容桂销售型网站建设/广州新一期lpr
  • 嘉兴快速建站合作/百度广告推广费用年费