HashMap的链表为什么要转成红黑树?

96 阅读4分钟

HashMap的链表为什么要转成红黑树?

  1. 优化查询性能
    链表的查询时间复杂度为 O(n),当链表长度较长时,查询效率会显著下降。而红黑树是一种自平衡二叉搜索树,其查询、插入和删除操作的时间复杂度均为 O(log n)。当链表长度超过阈值(默认为 8)时,将其转换为红黑树可以显著提升查询性能。

  2. 应对哈希冲突
    在某些极端情况下,如哈希函数设计不当或数据分布不均匀,可能会导致大量键值对映射到同一个桶中,从而使链表过长。将链表转换为红黑树可以有效缓解这种情况下的性能问题。

  3. 性能与空间的权衡
    虽然红黑树的节点大小约为普通链表节点的两倍,但只有在链表长度足够长时才会进行树化操作。这种设计既避免了不必要的空间开销,又能在需要时提升性能。

  4. 阈值的选择
    JDK 源码中提到,当链表长度达到 8 时,其在理想哈希分布下的概率仅为千万分之一。因此,设置 8 作为阈值既能避免不必要的树化操作,又能在极端情况下提供性能保障。

红黑树在 HashMap 中是如何维护平衡的?

在 Java 的 HashMap 中,红黑树的平衡维护是通过一系列的旋转操作和颜色调整来实现的。当链表转换为红黑树时,HashMap 使用了红黑树的自平衡特性来确保树的结构能够高效地支持插入、删除和查找操作。以下是红黑树在 HashMap 中维护平衡的具体方式:


1. 插入操作

当向 HashMap 中插入一个键值对时,如果链表长度超过阈值(默认为 8),链表会被转换为红黑树。插入操作遵循红黑树的插入规则:

步骤:

  1. 插入新节点
    新节点被插入到树中,初始颜色为红色。这是因为插入红色节点不会破坏红黑树的“黑色深度”属性(即从根到叶子的路径上黑色节点数量相同)。

  2. 检查并修复红黑树属性
    插入新节点后,可能会违反红黑树的规则,例如“连续两个红色节点”或“根节点必须是黑色”。此时,需要通过以下操作修复:

    • 颜色调整:改变某些节点的颜色。
    • 旋转操作:通过左旋或右旋调整树的结构。

旋转操作:

  • 左旋(Left Rotation)
    如果当前节点的右子节点是红色,且右子节点的右子节点也是红色,则进行左旋操作,将右子节点提升为父节点。

  • 右旋(Right Rotation)
    如果当前节点的左子节点是红色,且左子节点的左子节点也是红色,则进行右旋操作,将左子节点提升为父节点。

通过这些操作,可以确保插入操作后红黑树仍然保持平衡。


2. 删除操作

当从 HashMap 中删除一个键值对时,如果删除操作导致树的结构失衡,同样需要通过颜色调整和旋转操作来修复。

步骤:

  1. 删除节点
    删除目标节点,并根据其子节点数量选择合适的删除策略:

    • 如果删除的节点是叶子节点或只有一个子节点,直接删除。
    • 如果删除的节点有两个子节点,则用其后继节点(右子树中的最小节点)替换它。
  2. 修复红黑树属性
    删除操作可能会破坏红黑树的平衡,例如导致“黑色深度”不一致。此时需要通过以下操作修复:

    • 颜色调整:改变某些节点的颜色。
    • 旋转操作:通过左旋或右旋调整树的结构。

3. 平衡维护的规则

红黑树的平衡维护遵循以下规则:

  1. 根节点是黑色:插入或删除后,根节点必须是黑色。
  2. 叶子节点是黑色:叶子节点(空节点或NIL节点)是黑色。
  3. 红色节点的子节点是黑色:不能出现连续的红色节点。
  4. 从任何节点到其每个叶子的所有路径都包含相同数量的黑色节点:保持黑色深度一致。
  5. 通过旋转和颜色调整修复违反的规则:插入或删除操作后,通过左旋、右旋和颜色调整恢复红黑树的平衡。

4. 反树化(Ungrouping)

当从 HashMap 中删除键值对后,如果某个桶的红黑树节点数量减少到 6 或更少,红黑树会被转换回链表,以节省空间。


总结

HashMap 中,红黑树的平衡维护是通过插入和删除操作后的颜色调整和旋转操作来实现的。这些操作确保了红黑树始终满足其平衡性质,从而保证了 HashMap 在各种情况下都能高效地支持查找、插入和删除操作。