你真正了解Java中的TreeMap吗?

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/feilang00/article/details/86620391

image

1.概述

在本文中,我们将从Java Collections Framework(JCF)探索Map接口的TreeMap实现。

TreeMap是一个Map实现,它根据其键的自然顺序对其条目进行排序,或者如果用户在构造时提供比较器,则更好地使用比较器。

2. TreeMap中的默认排序

默认情况下,TreeMap根据其自然顺序对其所有条目进行排序。对于整数,这将表示升序和字符串字母顺序。

让我们看一下测试中的自然顺序:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect() {
    TreeMap<Integer, String> map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");
 
    assertEquals("[1, 2, 3, 4, 5]", map.keySet().toString());
}

请注意,我们以非有序的方式放置整数键,但在检索键集时,我们确认它们确实按升序维护。这是整数的自然排序。

同样,当我们使用字符串时,它们将按照自然顺序排序,即按字母顺序排序:

@Test
public void givenTreeMap_whenOrdersEntriesNaturally_thenCorrect2() {
    TreeMap<String, String> map = new TreeMap<>();
    map.put("c", "val");
    map.put("b", "val");
    map.put("a", "val");
    map.put("e", "val");
    map.put("d", "val");
 
    assertEquals("[a, b, c, d, e]", map.keySet().toString());
}

与散列映射和链接散列映射不同,TreeMap在任何地方都不使用散列原则,因为它不使用数组来存储其条目。

3.在TreeMap中自定义排序

如果我们对TreeMap的自然排序不满意,我们还可以在构建树图时通过比较器定义我们自己的排序规则。

在下面的示例中,我们希望整数键按降序排序:

@Test
public void givenTreeMap_whenOrdersEntriesByComparator_thenCorrect() {
    TreeMap<Integer, String> map = 
      new TreeMap<>(Comparator.reverseOrder());
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");
         
    assertEquals("[5, 4, 3, 2, 1]", map.keySet().toString());
}

哈希映射不保证存储的键的顺序,特别是不保证该顺序随时间保持不变,但树映射保证键将始终根据指定的顺序排序。

4. TreeMap排序的重要性

我们现在知道TreeMap按排序顺序存储它的所有条目。由于树图的这个属性,我们可以执行像: 找到“最大”、找到“最小”、找到小于或大于某个值的所有键等等。

以下代码仅涵盖了这些案例中的一小部分:·

@Test
public void givenTreeMap_whenPerformsQueries_thenCorrect() {
    TreeMap<Integer, String> map = new TreeMap<>();
    map.put(3, "val");
    map.put(2, "val");
    map.put(1, "val");
    map.put(5, "val");
    map.put(4, "val");
         
    Integer highestKey = map.lastKey();
    Integer lowestKey = map.firstKey();
    Set<Integer> keysLessThan3 = map.headMap(3).keySet();
    Set<Integer> keysGreaterThanEqTo3 = map.tailMap(3).keySet();
 
    assertEquals(new Integer(5), highestKey);
    assertEquals(new Integer(1), lowestKey);
    assertEquals("[1, 2]", keysLessThan3.toString());
    assertEquals("[3, 4, 5]", keysGreaterThanEqTo3.toString());
}

5. TreeMap的内部实现

TreeMap实现了NavigableMap接口,并使其内部基于红黑树的原理:

public class TreeMap<K,V> extends AbstractMap<K,V>
  implements NavigableMap<K,V>, Cloneable, java.io.Serializable

红黑树的原理超出了本文的范围,但是,为了理解它们如何适合TreeMap,需要记住一些关键事项。

首先,红黑树是由节点组成的数据结构; 图片是倒置的芒果树,其根在天空中,树枝向下生长。根将包含添加到树的第一个元素。

规则是从根开始,任何节点的左分支中的任何元素总是小于节点本身中的元素。右边的人总是更大。定义大于或小于的是由我们之前看到的元素的自然排序或构造中定义的比较器确定的。

此规则保证树图的条目始终按排序和可预测的顺序排列。

其次,红黑树是自平衡二叉搜索树。该属性和上述保证了搜索、获取、放置和删除等基本操作采用对数时间O(log n)。

自我平衡是关键。当我们继续插入和删除条目时,想象树在一条边上变长或在另一条边上变短。

这意味着操作将在较短的分支上花费较短的时间,而在距离根最远的分支上花费较长的时间,这是我们不希望发生的事情。

因此,这在红黑树的设计中得到了解决。对于每次插入和删除,任何边缘上树的最大高度都保持为O(log n),即树连续平衡。

就像散列映射和链接散列映射一样,树映射不同步,因此在多线程环境中使用它的规则与其他两个映射实现中的规则类似。

6.选择正确的Map

看过以前和现在的TreeMap的HashMap和LinkedHashMap实现,重要的是在三者之间做一个简短的比较,以指导我们哪一个适合哪里。

哈希映射可用作提供快速存储和检索操作的通用映射实现。然而,它不足是由于条目的混乱和不规则安排。

这导致它在存在大量迭代的情况下表现不佳,因为底层数组的整个容量影响遍历而不仅仅是条目数。

链接的哈希映射拥有哈希映射的良好属性并为条目添加顺序。它在有大量迭代的情况下表现更好,因为无论容量如何,只考虑条目数。

通过提供对键应如何排序的完全控制,树映射将排序提升到下一级别。另一方面,它提供了比其他两种替代方案更差的一般性能。

我们可以说链接的哈希映射减少了哈希映射的排序中的混乱,而不会导致树映射的性能损失。

7.结论

在本文中,我们探讨了Java TreeMap类及其内部实现。由于它是一系列常见Map接口实现中的最后一个,因此我们还简要讨论了与其他两个相关Map的最佳选择。


关注公众号:「Java知己」,每天更新Java知识哦,期待你的到来!

  • 发送「1024」,免费领取 30 本经典编程书籍。
  • 发送「Group」,与 10 万程序员一起进步。
  • 发送「JavaEE实战」,领取《JavaEE实战》系列视频教程。
  • 发送「玩转算法」,领取《玩转算法》系列视频教程。
    image
展开阅读全文

没有更多推荐了,返回首页