<think>我们正在讨论的是HashMap的源码解析。用户之前已经询问了ConcurrentHashMap,现在转向HashMap。根据引用内容,用户希望获取HashMap源码的详细解析。我们需要结合引用内容,覆盖HashMap的主要方面:结构、初始化、增删改查、扩容、树化等。
注意:引用中提到了JDK1.7和1.8的不同,因为JDK1.8中HashMap引入了红黑树,所以我们将主要解析JDK1.8的HashMap。
结构:
HashMap在JDK1.8中由数组+链表+红黑树组成。当链表长度超过8(且数组长度>=64)时,链表转换为红黑树;当树节点少于6时,红黑树退化为链表。
初始化:
有多个构造方法,可以指定初始容量和负载因子。如果不指定,默认初始容量为16,负载因子为0.75。构造方法中并没有直接创建数组,而是在第一次put时通过resize()方法创建。
主要方法:
put: 添加键值对
get: 根据键获取值
remove: 删除键值对
resize: 扩容
treeifyBin: 将链表转换为红黑树
由于用户要求详细解析,我们将按以下步骤进行:
1. 类结构:重要的静态内部类(Node, TreeNode)和字段(table, threshold, loadFactor等)
2. 初始化过程:构造方法如何计算初始容量(tableSizeFor方法)
3. 插入过程(putVal方法)详细步骤,包括哈希计算、处理碰撞、树化判断、扩容触发等
4. 扩容机制(resize方法):如何重新分配元素
5. 树化过程(treeifyBin方法)
6. 查找(get方法)和删除(remove方法)过程
由于源码较长,我们将摘取关键部分进行解释。
注意引用[1][2][3][4][5]中的内容,我们会适当引用并标注。
根据引用[1]:我们可以从类注释开始,掌握HashMap大致框架。
引用[4]提到了构造方法中指定初始容量和负载因子,以及tableSizeFor方法。
引用[5]对比了JDK1.7和1.8的结构变化(1.7是数组+链表,1.8是数组+链表+红黑树)。
现在开始解析:
</think>### HashMap 源码深度解析(JDK 1.8)
#### 1. **核心结构**
HashMap 由 **数组 + 链表 + 红黑树** 组成(JDK 1.8+):
- **数组**:`Node<K,V>[] table`,初始长度 16,存放链表头节点
- **链表**:解决哈希冲突,节点为 `Node` 类(含 `key`, `value`, `next` 指针)
- **红黑树**:当链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为 `TreeNode`
```java
// 节点定义
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // 链表指针
}
// 树节点定义(继承LinkedHashMap.Entry)
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // 额外prev指针用于快速拆树
boolean red;
}
```
---
#### 2. **初始化过程**
##### 2.1 构造方法
```java
// 关键参数
int threshold; // 扩容阈值 = capacity * loadFactor
final float loadFactor; // 负载因子(默认0.75)
// 常用构造器
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity");
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor");
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity); // 计算实际容量
}
```
##### 2.2 容量计算(`tableSizeFor`)
保证数组长度总是 **2 的幂**(便于位运算定位下标):
```java
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
```
> **示例**:输入 `initialCapacity=10` → 输出 `16`(大于 10 的最小 2 的幂)[^4]。
---
#### 3. **插入数据(`putVal`)**
流程如下:
1. 计算哈希值:`hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)`
2. 定位数组下标:`i = (n - 1) & hash`
3. 处理哈希冲突:
- 桶为空 → 直接插入新节点
- 桶为链表 → 遍历链表插入(或更新值)
- 桶为红黑树 → 调用 `TreeNode.putTreeVal()`
4. 触发扩容或树化
```java
final V putVal(int hash, K key, V value, boolean onlyIfAbsent) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; // 首次插入初始化数组
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); // CASE1:桶为空
else {
// CASE2/3:桶非空(链表或树)
Node<K,V> e; K k;
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p; // 头节点匹配
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 树插入
else {
// 链表遍历
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // 达到树化阈值
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && (...)) break; // 找到相同key
p = e;
}
}
if (e != null) { // 更新已存在key的值
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
return oldValue;
}
}
if (++size > threshold) resize(); // 触发扩容
return null;
}
```
---
#### 4. **扩容机制(`resize`)**
当元素数量 ≥ `threshold` 时触发:
1. 新容量 = 旧容量 × 2
2. 迁移数据(链表拆分为高低位链表):
```java
// 节点迁移逻辑(链表拆分)
Node<K,V> loHead = null, loTail = null; // 低位链表
Node<K,V> hiHead = null, hiTail = null; // 高位链表
do {
next = e.next;
// 判断节点归属(低位链表无需移动)
if ((e.hash & oldCap) == 0) {
if (loTail == null) loHead = e;
else loTail.next = e;
loTail = e;
} else { // 高位链表移动到新位置
if (hiTail == null) hiHead = e;
else hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
```
---
#### 5. **树化过程(`treeifyBin`)**
链表 → 红黑树的转换条件:
1. 链表长度 ≥ 8
2. 数组长度 ≥ 64(否则优先扩容)
```java
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) // 64
resize(); // 不满足条件时扩容
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null; // 构建TreeNode链表
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null) hd = p;
else { p.prev = tl; tl.next = p; }
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab); // 调用红黑树平衡方法
}
}
```
---
#### 6. **查找与遍历**
##### 6.1 查找(`get`)
```java
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
return first; // 检查头节点
if ((e = first.next) != null) {
if (first instanceof TreeNode) // 树查找
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do { // 链表遍历
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
```
##### 6.2 遍历(`keySet`)
```java
// 遍历示例(推荐使用EntrySet迭代器)
HashMap<String, String> map = new HashMap<>();
map.put("name", "Alice");
map.put("age", "30");
for (String key : map.keySet()) {
String val = map.get(key);
System.out.println(key + " : " + val); // 输出:name : Alice, age : 30
}
```
> **注意**:直接遍历 `keySet` 会触发多次哈希计算,建议用 `entrySet()`[^3]。
---
### 设计要点总结
| **特性** | **JDK 1.7** | **JDK 1.8** |
|------------------|--------------------------|----------------------------------|
| 数据结构 | 数组+链表 | 数组+链表+红黑树 |
| 哈希冲突解决 | 头插法 | 尾插法(避免死循环) |
| 节点定位 | `indexFor()` | `(n-1) & hash`(位运算优化) |
| 扩容后节点迁移 | 全部重新哈希 | 高低位链表拆分(性能提升 50%) |
| 树化条件 | 不支持 | 链表≥8 且 数组长度≥64 |