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

分析一下HashMap内部是怎么实现的

当然可以!我们来深入分析一下 Java 中 HashMap 的内部实现机制(以 JDK 8 为主),包括数据结构、核心算法、源码设计、以及适用场景。


🧠 一、HashMap 的核心结构

HashMap 是基于哈希表实现的 Map,底层结构是数组 + 链表 + 红黑树的结合体:

✅ 数据结构

transient Node<K,V>[] table; // 主体数组,哈希桶(bucket)
  • 每个数组元素称为一个“桶”(bucket)。
  • 每个桶可能为空,也可能是:
    • 链表结构:用于解决哈希冲突;
    • 红黑树结构:当链表过长(默认长度 > 8)时转为红黑树,提高查询效率。

🔧 二、HashMap 的核心操作

1️⃣ put(K key, V value) —— 添加元素

主要流程:

  1. 计算 key 的 hash 值并映射到桶

    int hash = hash(key);
    int index = (n - 1) & hash; // 取模操作(位运算优化)
    
  2. 判断是否存在冲突

    • 若该桶为空,直接新建节点放入。
    • 若已存在节点:
      • key 已存在 ⇒ 覆盖旧值;
      • key 不存在 ⇒ 添加到链表尾部或插入红黑树。
  3. 链表长度超过阈值(8)

    • 且数组长度 >= 64 时 ⇒ 转为红黑树
    • 否则还是保持链表。
  4. 是否触发扩容?

    • 若当前元素个数 > threshold(一般为 loadFactor × capacity) ⇒ 触发 resize()

2️⃣ get(Object key) —— 查找元素

  1. 计算 hash ⇒ 定位到桶;
  2. 遍历桶内链表或红黑树,比较 key 是否相等;
  3. 找到即返回 value,未找到返回 null。

3️⃣ resize() —— 扩容机制

  • 扩容一般是原容量的 2 倍
  • 将旧数组的所有元素重新 hash 分配到新数组;
  • 扩容是最消耗性能的操作之一,会导致 O(n) 的开销;
  • 所以建议创建 HashMap 时合理指定初始容量。
int newCap = oldCap << 1;

📦 三、Node 节点结构(源码简化)

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next; // 链表结构
}

若该桶变为红黑树,则会变成 TreeNode(继承 Node):

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    boolean red;
}

🧠 四、哈希函数和哈希扰动

为了避免哈希冲突,JDK 8 对 hashCode() 结果做了“扰动处理”:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这种处理方式能让高位参与低位运算,提高 hash 值的随机性,从而减少哈希冲突


🚩 五、红黑树的使用条件

条件是否转为红黑树
链表长度 > 8,且数组长度 >= 64✅ 转为红黑树
链表长度 > 8,数组长度 < 64❌ 不转,优先扩容
树节点个数 < 6✅ 转回链表(treeify → untreeify)

这样做的好处是避免在小容量下使用红黑树带来的额外复杂度和性能负担。


📈 六、时间复杂度分析

操作最佳情况最坏情况(大量哈希冲突)
put/get/removeO(1)O(log n)(红黑树)或 O(n)(链表)
resizeO(n)O(n)

📌 七、HashMap 线程安全性

  • 非线程安全!
    • 多线程下容易出现 数据覆盖死循环(JDK 7)、丢失更新等问题。
  • 如果多线程环境:
    • 可使用:Collections.synchronizedMap(new HashMap())
    • 或直接用:ConcurrentHashMap(更高效,分段锁/红黑树优化)

✅ 八、应用场景

适合以下场景:

  • 查找、插入、删除频繁且对性能要求高;
  • Key 无序、无需排序;
  • Key 避免过多 hash 冲突(选择好的 hashCode() 方法);
  • 单线程或开发者自行管理并发问题。

🧪 九、底层源码建议阅读入口(JDK 8)

HashMap.java 的以下方法入手:

  • putVal(...):添加元素的核心逻辑
  • resize():扩容处理
  • treeifyBin(...):链表转红黑树
  • getNode(...):查找逻辑

http://www.dtcms.com/a/122405.html

相关文章:

  • JavaScript Date(日期)
  • HTTPS为何仍有安全漏洞?解析加密协议下的攻击面
  • Java后端开发-面试总结(集结版)
  • 11. git restore
  • VLLM V1 part 4 - KV cache管理
  • 数据库无法插入中文字符
  • Spring MVC 处理 HTTP 状态码、响应头和异常的完整示例
  • 12.实现一个简单的依赖注入容器
  • [免费]SpringBoot+Vue高考志愿填报系统【论文+源码+SQL脚本】
  • MySQL | 三大日志文件
  • KHARPA币:结合传统与区块链技术的DeFi DAO革命
  • Houdini20.5apex绑定模块入门学习笔记
  • 参考平面跨分割情况下的信号回流
  • 落地DevOps文化:运维变革的正确打开方式
  • C#里设计Modbus-RTU(Remote Terminal Unit)协议
  • STM32——RTC实时时钟
  • Windows 部署项目 apache + mod_wsgi,nginx + waitress
  • 栈与堆的本质区别:深入理解 Rust 的内存管理模型
  • Xilinx虚拟输入/输出(VIO)IP核详细介绍及使用示例
  • Smith-Waterman 算法(C++实现)
  • SpringBoot 接口限流Lua脚本接合Redis 服务熔断 自定义注解 接口保护
  • postman 安装及使用 [软件测试工具]
  • 如何根据不同文字内容批量生产手写的图片,模拟真人写的笔记(待验证)
  • 代码随想录算法训练营Day24
  • 第1章 对大型语言模型的介绍
  • SQL优化技术分享:从 321 秒到 0.2 秒的性能飞跃 —— 基于 PawSQL 的 TPCH 查询优化实战
  • 栈与队列及其基础应用
  • 【Kafka基础】topic命令行工具kafka-topics.sh:基础操作命令解析
  • STM32低功耗
  • 数据结构--堆