HashMap 的实现原理主要基于哈希表,它通过哈希函数将键(key)映射到数组中的一个位置,从而实现对数据的快速存取。以下是 HashMap 实现原理的详细解析:
1. 数据结构
HashMap 的底层实现是一个数组,但每个数组元素可以是一个链表或红黑树(在 Java 8 及以上版本中)。这种结构被称为“链表散列”或“哈希桶”。
- 数组:HashMap 初始化时会创建一个初始容量的数组,这个数组的长度通常是 2 的幂次方,以保证哈希分布的均匀性和高效性。
- 链表/红黑树:当多个键的哈希值相同时(即哈希冲突),这些键会被存储在同一个数组位置上的链表或红黑树中。在 Java 8 之前,链表是处理哈希冲突的唯一方式;而在 Java 8 及以后版本中,当链表长度超过一定阈值(默认为 8)时,链表会被转换为红黑树,以提高搜索和插入的效率。
2. 哈希函数
HashMap 使用哈希函数来计算键的哈希值,并将这个哈希值映射到数组中的一个索引位置。Java 中的 hashCode()
方法用于生成键的哈希码,而 HashMap 中的哈希函数则基于这个哈希码进行进一步的处理,以确保索引位置的均匀分布。
3. 索引计算
HashMap 通过哈希值和数组长度来计算键在数组中的索引位置。具体来说,它使用哈希值对数组长度取模(在 Java 中,实际上是通过位运算 h & (length-1)
来实现,因为数组长度是 2 的幂次方时,这种位运算等价于取模)来得到索引位置。
4. 存储过程
- 当向 HashMap 中添加一个新的键值对时,首先计算键的哈希值,并找到对应的索引位置。
- 如果该索引位置上没有元素,则直接将键值对存储在该位置上。
- 如果该索引位置上已经存在元素(链表或红黑树),则遍历链表或树,查找是否已经存在相同的键。
- 如果存在,则更新对应的值。
- 如果不存在,则将该键值对添加到链表或树的末尾。
- 如果链表长度超过了阈值(Java 8 及以上版本为 8),则将链表转换为红黑树。
5. 扩容机制
当 HashMap 中的元素数量超过了负载因子(默认为 0.75)与数组大小的乘积时,会触发扩容操作。扩容操作会创建一个新的、大小为原数组两倍的新数组,并将原数组中的所有元素重新计算哈希值后,放入新数组的相应位置上。这个过程涉及到数据的复制和重新分配内存,因此需要消耗一定的时间和空间。