Java HashMap的实现原理
概述
- HashMap是基于哈希表的Map接口的非同步实现。
- 它允许使用null值和null键。
- HashMap不保证映射的顺序,特别是它不保证该顺序恒久不变。
- HashMap假定哈希函数将元素适当地分布在各桶之间,为get和put操作提供稳定的性能。
数据结构
- HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
- 在JDK 1.7及之前,HashMap底层是一个数组+链表结构。
- 在JDK 1.8及之后,HashMap引入了红黑树以优化性能。当某个桶(数组的一个位置)的链表长度大于等于8并且HashMap的数组长度大于等于64时,链表会转换为红黑树。
哈希冲突处理
- 当两个或多个键的哈希值相同时,会发生哈希冲突。
- HashMap使用链地址法(Chaining)来处理哈希冲突。这意味着当冲突发生时,冲突的键值对会被存储在同一个桶的链表中(或红黑树中)。
存储与查找
- 当存储一个键值对时,首先根据键的哈希值找到对应的桶。
- 如果桶为空,则直接将键值对插入桶中。
- 如果桶非空,则遍历桶中的链表或红黑树:
- 如果键已经存在,则替换其值。
- 如果键不存在,则将键值对插入链表或红黑树的末尾。
- 查找操作与存储操作类似,也是根据键的哈希值找到对应的桶,然后遍历链表或红黑树来找到对应的值。
扩容机制
- HashMap在创建时可以指定初始容量和加载因子。
- 当HashMap中的元素数量超过初始容量乘以加载因子时,会进行扩容。
- 扩容时,HashMap的容量会变为原来的两倍,并重新计算所有元素的哈希值以重新分布到新的桶中。
性能特点
- 由于HashMap使用了哈希表和链表(或红黑树)的结合,因此它在大多数情况下提供了接近O(1)的插入、获取和删除性能。
- 但需要注意的是,HashMap的性能受到哈希函数质量、加载因子和初始容量的影响。
注意事项
- HashMap不是线程安全的。如果多个线程同时访问一个HashMap,并且至少有一个线程从结构上修改了它(即添加或删除了一个或多个映射关系),则必须在外部保持同步以防止意外的非同步访问。
补充概念:加载因子
加载因子(Load Factor)是HashMap中的一个重要参数,用于确定HashMap的扩容阈值。以下是关于加载因子的详细解释:
- 定义:
加载因子是一个浮点数,用于控制HashMap的扩容行为。它决定了HashMap在其容量自动增加之前可以达到多满的一种尺度。 - 计算公式:
加载因子 = 元素个数 / 容量
当HashMap中的元素个数超过容量与加载因子的乘积时,即达到了扩容阈值,HashMap会进行扩容操作。 - 默认值:
在Java中,HashMap的默认加载因子是0.75(即75%)。
默认初始容量是16,因此默认的扩容阈值是 16 * 0.75 = 12。也就是说,当HashMap中的元素个数达到12时,它会进行扩容操作。 - 作用与影响:
加载因子的选择是一个权衡的结果,它既要保证HashMap的性能又要节约内存空间。
较低的加载因子可以减少哈希碰撞的概率,提高HashMap的性能,但会增加扩容的频率,从而增加时间开销和内存使用。
较高的加载因子可以节约内存空间,减少扩容的频率,但会增加哈希碰撞的概率,降低HashMap的性能。 - 自定义:
HashMap允许用户自定义加载因子。在创建HashMap时,可以通过构造函数指定初始容量和加载因子。
例如:HashMap<K, V> map = new HashMap<>(int initialCapacity, float loadFactor); - 总结:
加载因子是HashMap中的一个重要参数,用于控制HashMap的扩容行为。
它通过权衡性能和内存使用来确定HashMap何时进行扩容操作。
默认的加载因子是0.75,但用户可以根据具体的应用场景和需求进行自定义。