HashMap 在 Java 中的底层实现细节随着版本的不同有所变化,这里主要介绍 JDK1.8 及之后版本的 HashMap 底层实现:
在 JDK1.8 及以后版本中,HashMap 的底层数据结构结合了数组和链表/红黑树两种方式:
- 数组(Node数组):
HashMap 的核心是一个动态扩容的数组,数组中的每个元素都是一个 Node 类型的对象(在内部称为桶或槽)。Node 是一个内部类,它包含 key、value 和两个引用字段:next
用于链接下一个节点(处理哈希冲突),以及hash
字段存储计算出来的哈希值。 - 链表与红黑树转换:
当多个键映射到同一个索引位置时(即发生哈希碰撞),这些键值对会以链表的形式存储在这个索引位置对应的桶上。当链表长度超过阈值(默认为 8)时,HashMap 会将这个链表转换为一颗红黑树来优化查找和插入操作的时间复杂度。红黑树是一种自平衡二叉查找树,这样即使在最坏的情况下,也能保证基本的操作(如 get 和 put)时间复杂度为 O(log n)。 - 哈希函数计算索引:
对于插入的每一个键,HashMap 首先调用其hashCode()
方法得到原始哈希码,然后通过hash()
函数进行扰动运算(目的是让分布更均匀),最终使用(n - 1) & hash
计算出该键值对在数组中的索引位置。这里的n
是当前数组的大小(总是2的幂次方),&
操作符用来获取哈希码对应数组索引的有效部分。 - 容量和负载因子:
HashMap 的容量可以在创建时指定,如果不指定,默认初始容量是 16。负载因子(default load factor)默认是 0.75。当实际存储的元素数量超过容量与负载因子相乘的结果时(即size > capacity * loadFactor
),HashMap 会自动扩容至原来容量的两倍,并重新调整所有已有元素的位置。 - 线程安全性:
HashMap 并不是线程安全的,在并发环境下直接操作可能会导致数据不一致或其他不可预期的行为。若需要线程安全的 Map,应该使用 ConcurrentHashMap 或 Collections.synchronizedMap 包装后的 HashMap。