1. HashMap概述
HashMap
是基于哈希表的Map
接口实现,允许空键和空值。它继承自AbstractMap
,实现了Map
、Cloneable
和Serializable
接口。
2. 底层数据结构
在JDK 1.8中,HashMap
的底层数据结构由数组 + 链表 + 红黑树构成:
-
数组:存储哈希表的节点(
Node
)。 -
链表:解决哈希冲突,当多个键的哈希值相同或相近时,它们会被存储在同一个数组槽位的链表中。
-
红黑树:当链表长度超过一定阈值(默认为8)时,链表会被转换为红黑树,以提高操作效率。
3. 构造函数
HashMap
提供了多个构造函数,最基础的构造函数如下:
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;
this.threshold = tableSizeFor(initialCapacity);
}
-
initialCapacity
:初始容量,决定了哈希表的初始大小。 -
loadFactor
:加载因子,用于控制哈希表的扩容。
4. 关键方法解析
4.1 put()
方法
put()
方法用于将键值对插入到哈希表中:
-
计算哈希值:通过
hash()
方法计算键的哈希值。 -
定位数组槽位:通过
(n - 1) & hash
计算键在数组中的位置。 -
处理冲突:如果槽位已有数据,通过链表或红黑树解决冲突。
-
扩容:如果哈希表中的元素数量超过阈值,会触发
resize()
方法进行扩容。
4.2 get()
方法
get()
方法用于根据键获取值:
-
计算哈希值:通过
hash()
方法计算键的哈希值。 -
定位数组槽位:通过
(n - 1) & hash
找到键所在的数组槽位。 -
查找键值对:在链表或红黑树中查找键对应的值。
4.3 resize()
方法
resize()
方法用于扩容哈希表:
-
创建新数组:创建一个容量为原数组两倍的新数组。
-
迁移数据:将原数组中的数据迁移到新数组中。
-
更新阈值:更新扩容阈值为新数组容量与加载因子的乘积。
5. 哈希冲突处理
HashMap
通过以下方式处理哈希冲突:
-
链表:当多个键的哈希值相同或相近时,它们会被存储在同一个数组槽位的链表中。
-
红黑树:当链表长度超过8时,链表会被转换为红黑树,以提高操作效率。
6. 性能优化
HashMap
通过以下方式优化性能:
-
负载因子:通过设置合适的负载因子,控制哈希表的扩容时机,避免过度扩容。
-
即时编译(JIT):JVM的即时编译器会优化
HashMap
的热点代码,提高执行效率。
7. 线程安全性
HashMap
是非线程安全的,多个线程同时操作HashMap
可能会导致数据不一致。如果需要线程安全的哈希表,可以使用ConcurrentHashMap
。
8. 总结
-
底层数据结构:数组 + 链表 + 红黑树。
-
关键方法:
put()
、get()
、resize()
。 -
哈希冲突处理:链表和红黑树。
-
性能优化:负载因子和即时编译。
-
线程安全性:非线程安全,推荐使用
ConcurrentHashMap
。