HashMap底层是数组+链表/红黑树结构,通过哈希函数定位桶,用链表解决冲突,链表≥8且数组≥64时转红黑树,需重写hashCode()和equals(),超阈值触发扩容。

HashMap 的底层哈希表实现,本质是用“数组 + 链表/红黑树”模拟一张逻辑上的散列表(Hash Table),核心目标是把任意键(Key)快速映射到一个固定位置,从而实现平均 O(1) 的存取效率。
数组是主干,“桶”决定落点
HashMap 底层维护一个 Node<K,V>[] table 数组,每个数组元素叫一个“桶(bucket)”。这个数组不是按顺序存数据的,而是靠键的哈希值算出下标——比如键 "user123" 经过 hashCode() 和扰动运算后得到哈希值 1987,再对数组长度取模(如 1987 % 16 = 3),就落到索引为 3 的桶里。这个过程就是哈希函数定位。
冲突不可避免,链表和红黑树来兜底
不同键可能算出相同下标(哈希冲突),比如 "abc" 和 "def" 都落到索引 5。这时不能覆盖,而是把新节点接在原节点之后,形成链表:
- 默认插入链表头部(JDK 7 是头插,JDK 8 改为尾插,避免并发扩容时的环形链表问题)
- 当链表长度 ≥ 8 且 数组长度 ≥ 64 时,链表自动转为红黑树,提升查找性能(从 O(n) 降到 O(log n))
- 若树中节点数 ≤ 6,则退化回链表,避免小数据量时树结构开销过大
键的比较必须成对重写:hashCode() + equals()
HashMap 判断两个键是否相等,分两步走:
- 先比
hashCode():不等 → 肯定不同键 → 直接放入新桶或链表尾 - 若相等 → 再调用
equals()比内容:返回 true → 视为重复键,覆盖 value;返回 false → 视为不同键,链表/树中新增节点
所以自定义类作 Key 时,必须同时重写 hashCode() 和 equals(),否则哪怕内容一样,也会被当成两个不同键存两次。
扩容不是小事,影响性能的关键环节
初始容量默认是 16,加载因子默认是 0.75。当 size >= capacity × loadFactor(即元素数 ≥ 12)时,触发扩容:
- 新建一个容量翻倍的数组(如从 16 扩到 32)
- 所有已有 Node 重新计算哈希、重新取模,分配到新数组对应桶中
- 链表或红黑树节点也跟着迁移,红黑树还会在迁移中做平衡调整
扩容耗时,所以如果预估数据量大,建议初始化时指定合理初始容量(如 new HashMap(1024)),减少 resize 次数。

















