甘南藏族自治州网站建设_网站建设公司_留言板_seo优化
2025/12/20 17:36:16 网站建设 项目流程

详细介绍:Java基础篇——一文搞懂 HashMap 底层原理

2025-12-20 17:31  tlnshuju  阅读(0)  评论(0)    收藏  举报

HashMap 是 Java 开发中使用频率最高、最核心的集合之一,但它的底层细节非常多:数组、链表、红黑树、哈希函数、扩容机制、并发问题…… 要真正掌握 HashMap,背后这套体系必须吃透。

本文将从 数组结构、哈希寻址、扩容原理、树化机制、并发问题 五个维度系统讲透 HashMap 的底层原理,让你从使用者进阶到理解者。


前置知识(非常重要)、什么是线程安全?

首先要明白一点,线程安全本身说的并不是线程是否安全,而是指的内存是否安全。

其实本质就是对于操作系统中的堆内存区域其实是允许共享访问的,这时候如果不加锁的限制,就容易出现错乱。

线程安全的解决思路:

1.theredLocal(每个线程有自己一个独立副本)

2.局部变量(私有化,只能自己处理)

3.final(只能读不能写)

4.加锁(互斥锁,悲观锁)——适用于线程多的情况

5.失败重试的机制(CAS,乐观锁)——适用于线程少的情况,因为线程越少,发生不安全的概率也就越少

一、HashMap 的底层结构

JDK7:数组 + 链表
JDK8:数组 + 链表 + 红黑树

HashMap 的本质是一张 "数组 + 链表/红黑树" 的混合结构:

  • 数组:真正存储数据的地方,称为 桶(bucket)

  • 链表 / 红黑树:用于解决哈希冲突的结构。

当多个键算出的下标相同,就会进入同一个桶:

  • 冲突不多:链表

  • 冲突多(链表长度 ≥ 8 且数组容量 ≥ 64):红黑树

树化是 JDK8 最大的优化点之一,减少了极端情况下查询退化到 O(n) 的风险。


二、元素添加的完整流程(核心)

1. put(k, v) 大致步骤:

  1. 根据 key 的 hash 值计算数组下标:

    index = (n - 1) & hash
  2. 如果桶为空:直接放进去

  3. 如果桶非空:

    • 遍历链表/红黑树

    • 若发现 key 一致,用新 value 覆盖旧 value

    • 若没有重复 key:放到链表尾(JDK8)

  4. 插入后判断是否需要树化

  5. 判断是否达到扩容阈值并进行扩容

为什么用 (n - 1) & hash

这是 HashMap 最经典的优化点。

例如默认数组长度 n = 16:

  • n - 1 = 15(二进制:0000 1111)

  • 任何数字与 0000 1111 按位与 (&) 后:

    • 只保留低四位

    • 结果一定在 0 ~ 15 的范围内

也就是说,下标范围刚好落入数组范围,不会越界。

更重要的是:

  • 这个运算在效果上 ≈ hash % n (本质上就是模一个16,16进制的余数,只是性能更高)

  • 位运算 & 的效率远高于 % 取余运算

因此 HashMap 使用这种技巧来提高寻址效率。


三、扩容机制(HashMap 性能关键)

1. 扩容时机

当元素数量达到:

size ≥ 容量 × 0.75

就会触发扩容。

这个 0.75 就是 HashMap 的 负载因子

2. 为什么负载因子是 0.75?

它不是拍脑袋取的,是大量实验和数学统计(泊松分布)后的最佳平衡点:

  • < 0.75:空间浪费严重

  • 0.75:哈希冲突增加导致性能下降

0.75 是 HashMap 在 性能空间 的最优折中点。

3. 为什么扩容为 2 倍?

简单来说就是让二进制的末尾都是1,这样就可以用&运算去替代取模,从而提升性能。

扩容后数组大小仍保持 2 的次幂:例如 16 → 32 → 64 …

因为只有当数组长度是 2 的次幂时:

(n - 1) 的二进制全部为 1

例如:

  • n = 16(0001 0000)

  • n - 1 = 15(0000 1111)

此时:

(n - 1) & hash

的结果取决于 hash 的低位值,能保证:

  • 分布更均匀

  • 冲突更低

这就是 HashMap 必须用 2 的幂长度数组的根本原因

4. rehash的内容

触发扩容的本质其实是想把链表的长度给降下来,这时候重新分配就是rehash。

jdk7:采用重新按位与的策略分配到新的内存上。

jdk8:采用和原空间大小按位与,区分低位和高位,如果是16代表是高位,放到新的下标位置(原下标+扩容的大小),如果<16,保持原来下标位置。

举个例子:当前是12的位置,此时&16如果得到16:

高位——>新的下标(12 + 16)

低位——>原下标


四、ArrayList 与 HashMap 的容量机制对比

首先明白一点:ArrayList是采用一个懒加载策略,初始化完后不分配大小,第一次add的时候分配10的大小。

这一部分帮助你建立对集合“扩容成本”的整体认知。

ArrayList 为什么建议初始化容量?

因为 ArrayList 内部本质是 动态数组。如果不设置容量:

  • 一直 add 时会触发扩容

  • 扩容需要 新建更大的数组 + 拷贝旧数组数据

扩容代价很高,因此建议:

如果能预估数据量,尽量指定初始容量

ArrayList 扩容为 1.5 倍的原因

仍然是平衡:

  • 扩太多 → 浪费内存

  • 扩太少 → 频繁扩容导致性能下降

ArrayList 线程安全吗?

不安全,不安全正是因为对堆内存的并发修改。

多线程 add 时会出现:

场景1:

  • 元素覆盖(两线程同时获取到了地址0,一个add了1,一个add了2,2覆盖了1的内容)

  • 数据丢失(两线程同时获取到了地址0,一个add了1,一个add了2,1丢失了)

参考下图:

场景2:

  • 扩容并发表现 undefined(不可预期)

  • 说明:

    线程 A 正在 copy:

    copy oldArr[0]
    copy oldArr[1]
    (copy 一半的时候)

    线程 B 正在 add:

    oldArr[size++] = value

    结果导致:

  • 1.首先如果数组中A有3个元素,但是copy过程中,B加入了新的,这时候最后也只有三个元素
  • 2.更可怕的是:如果这时候我们拷贝了地址为2的区域,但在这时B也恰好执行oldArr[2] = newvalue,这时候很难保证扩容的数组2号地址是什么值,这是竞态条件。
    • 元素丢失

    • 元素重复

    • 数组状态不一致

    • list 结构损坏

解决方案:

  • synchronized 包裹

  • CopyOnWriteArrayList


五、CopyOnWriteArrayList 原理(读多写少场景神器)

核心思想:写时复制

写操作:

  1. 加锁

  2. copy 原数组 -> 新数组

  3. 在新数组上写入

  4. 指针指向新数组

读操作:

  • 不加锁!直接读取旧数组,因此读非常快

缺点:

  • 占内存

  • 读到的数据不是强一致

适合场景:读多写少


六、HashMap 的线程安全问题

HashMap 本质是非线程安全的。

JDK7 问题

  • 扩容可能产生“环形链表”,导致死循环

原因是用了头插法,本质就是:两个线程同时迁移链表导致的“指针篡改”问题

JDK8 问题

  • 并发 put 导致数据覆盖(类似arraylist的add)

因此:

  • 多线程不要用 HashMap!

  • 改用 ConcurrentHashMap


七、ConcurrentHashMap(线程安全的 HashMap)

JDK 1.7:分段锁(Segment)

  • 分成 16 段,每段一把锁

  • 并行度最多 16

JDK 1.8:CAS + synchronized

特点:

  • 不再使用 Segment

  • 针对每个桶(链表/树)头节点加锁

  • 大部分操作依赖 CAS

  • 锁粒度更细

为什么不全部使用 CAS?

因为:

  • CAS 适合 短时间的、简单的操作(比如初始化桶)

  • synchronized 适合 复杂、耗时的操作(比如遍历链表 + 插入 + 树化)

这体现了现代并发容器的理念:

无锁优先,锁为补充


总结:HashMap 的设计哲学

复盘整个 HashMap,你会发现它有三大核心设计理念:

① 数学 + 工程优化结合

  • 2 的次幂数组长度

  • (n-1)&hash 寻址

  • 负载因子 0.75

  • 红黑树优化极端情况

② 高效与扩展性的平衡

  • 链表 + 数组 + 红黑树组合

  • 位运算替代取余

  • 两倍扩容减少复杂重分布

③ 并发编程思想

  • HashMap 本身不保证线程安全

  • ConcurrentHashMap 将“无锁 + 有锁”混合使用,取长补短


如果你认真读完本篇文章,你已经真正理解了 HashMap,而不是停留在“数组 + 链表 + 红黑树”的表层知识。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询