MST

星途 面试题库

面试题:Java HashMap中解决哈希冲突的再哈希法在JDK中的体现

在Java的HashMap中并没有显式地使用传统的再哈希法来解决哈希冲突,但存在类似机制。请分析JDK中HashMap是如何通过扩容等手段来降低哈希冲突影响,类似于再哈希法的效果,并说明扩容过程中数据迁移和哈希冲突处理的细节。
47.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 扩容降低哈希冲突原理

在Java的HashMap中,通过扩容来降低哈希冲突影响。HashMap有两个重要参数:容量(capacity)和负载因子(load factor)。当HashMap中的元素数量(size)达到容量与负载因子的乘积(即size >= capacity * load factor)时,就会触发扩容。扩容后,新的容量是原来容量的两倍。

扩容类似于再哈希法,因为扩容后重新计算元素在新数组中的位置,元素的哈希值不变,但在新的更大的数组中,通过重新计算索引,使得哈希冲突的概率降低。例如,原本冲突在同一个桶(bucket)中的元素,扩容后可能会分散到不同的桶中。

2. 扩容过程中数据迁移细节

  • 创建新数组:扩容时,会创建一个新的Node数组,其容量为原数组容量的两倍。
  • 重新计算索引:遍历原数组中的每个桶,对于每个桶中的链表(或红黑树,JDK 8+),会重新计算每个元素在新数组中的索引位置。在JDK 8之前,是通过(n - 1) & hashn为新数组容量)来计算索引;JDK 8及之后,为了优化计算过程,对于2的幂次方容量的数组,当(hash & oldCap) == 0时,新的索引位置与旧索引位置相同;当(hash & oldCap) != 0时,新的索引位置为旧索引位置加上旧数组容量。
  • 迁移元素:将原数组中的元素迁移到新数组对应的位置。如果原桶中是链表结构,在JDK 8之前,会直接将链表中的元素依次重新计算索引并插入到新数组对应位置;JDK 8之后,会将链表拆分成两条链表,一条是索引不变的链表,另一条是索引需要加上旧数组容量的链表,然后分别插入到新数组对应的位置。如果原桶中是红黑树结构,也会类似地重新计算索引并迁移元素,当迁移后节点数量小于等于6时,会将红黑树转换回链表。

3. 扩容过程中哈希冲突处理细节

扩容过程中,通过重新计算索引,原本冲突的元素可能会被分散到不同的桶中,从而减少哈希冲突。在数据迁移时,如果新计算的索引位置已经有元素(发生冲突),在链表结构下,新元素会被插入到链表头部(JDK 8之前)或尾部(JDK 8及之后);在红黑树结构下,会按照红黑树的插入规则插入元素,以维护红黑树的平衡性质。