Java 1.8 引入了对 HashMap
的一些重要改进,特别是在处理哈希冲突和性能方面。以下是对 HashMap
1.8 实现逻辑的分析。
1. 数据结构
HashMap
的主要数据结构是数组和链表(或红黑树):
- 数组:用于存储桶(buckets),每个桶可以包含多个键值对。
- 链表:在每个桶中,使用链表来存储哈希冲突的键值对。
- 红黑树:当链表的长度超过阈值(默认为 8)且当前容量(即桶的数量)达到一定阈值(默认为 64)时,链表会转换为红黑树,以提高查找效率。
2. 重要字段
HashMap
的核心字段包括:
Node<K,V>[] table
:存储桶的数组。int size
:当前映射中键值对的数量。int threshold
:桶的阈值,当数组的大小超过这个值时,HashMap
会进行扩容。float loadFactor
:负载因子,决定了何时进行扩容,默认值为 0.75。
3. 构造函数
HashMap
提供多个构造函数,允许用户指定初始容量和负载因子。默认情况下,初始容量为 16,负载因子为 0.75。
4. 哈希函数
HashMap
使用 hash(Object key)
方法来计算键的哈希值。它会对键的哈希值进行处理,以减少碰撞的可能性。具体实现如下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这个方法通过 XOR 操作和右移操作来增强哈希值的分布性。
5. 插入元素
插入元素的过程如下:
- 计算哈希值:使用
hash()
方法计算键的哈希值。 - 确定桶的位置:通过
indexFor(hash, table.length)
方法计算元素应该放置的桶的索引。 - 处理冲突:
- 如果桶为空,直接插入。
- 如果桶不为空,遍历链表检查是否存在相同的键:
- 如果找到相同的键,更新值。
- 如果没有找到,插入到链表的末尾。
- 如果链表长度超过 8且前桶的数量大于等于 64,转换为红黑树。
6. 查找元素
查找元素的过程如下:
- 计算哈希值:使用
hash()
方法计算键的哈希值。 - 确定桶的位置:通过
indexFor(hash, table.length)
方法计算桶的索引。 - 遍历桶:
- 如果桶为空,返回 null。
- 如果桶中是链表,遍历链表查找键。
- 如果桶中是红黑树,使用树的查找方法。
7. 扩容
当 HashMap
中的元素数量超过 threshold
时,会触发扩容。扩容的过程如下:
- 新建一个更大的数组:通常是原数组大小的两倍。
- 重新计算每个元素的位置:遍历原数组的每个桶,将元素重新插入到新的桶中。
- 更新阈值:新的阈值为新数组大小乘以负载因子。
8. 性能优化
- 链表到红黑树的转换:当链表长度超过 8 时且前桶的数量大于等于 64,链表会转换为红黑树,以提高查找效率,从 O(n) 降低到 O(log n)。
- 使用
Node
类:HashMap
1.8 使用Node
类来表示每个键值对,简化了结构。