一、HashMap 概述
在 Java 中,HashMap
是一个非常常用的 Map
接口的实现类,它基于哈希表(数组 + 链表/红黑树)实现键值对存储,提供 O(1)
时间复杂度的查找、插入、删除操作。
本文将基于 JDK 17 源码,详细分析 HashMap
的内部结构、关键方法以及扩容机制。
二、HashMap 数据结构
在 JDK 17 中,HashMap
的核心数据结构是一个 Node<K,V>[]
类型的数组。每个节点(Node
)保存一个键值对,并且包含一个指向下一个节点的引用(在哈希冲突时,形成链表)。
transient Node<K,V>[] table;
2.1 Node 类
Node
是 HashMap
内部存储的基本单元,表示一个键值对。每个 Node
包括键、值、哈希值和指向下一个节点的指针:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
2.2 HashMap 数组和链表
HashMap
使用一个数组来存储键值对,每个数组元素可能是一个链表的头节点。在哈希冲突时,多个键值对会被链式存储在同一个数组位置。自 JDK 1.8 以来,当链表长度超过一定阈值(默认为 8),链表会被转化为红黑树,优化查找效率。
2.3 红黑树
在 JDK 17 中,如果哈希冲突导致某一槽位的链表长度超过 8,链表会转化为红黑树。红黑树的引入有效提升了高哈希冲突情况下的查询性能,最坏情况下的查找时间复杂度由链表的 O(n)
降低到了 O(log n)
。
三、HashMap 关键方法分析
3.1 构造 方法
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY(最大容量))
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//tableSizeFor方法会返回找到大于或等于 initialCapacity 的最小2的幂
//也就是数组长度始终为2^n
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR); //调的双参的构造方法
}
public HashMap() {
//此时不会显示初始化,在put第一个元素的时候才会初始化
this.loadFactor = DEFAULT_LOAD_FACTOR;