概述

在这个教程中,我们将展示如何使用Google Guava库中的RangeMap接口及其实现。

RangeMap是一种特殊的映射,它将不重叠的非空区间映射到非空值。通过查询,我们可以查找映射中任何特定区间的值。

2. Google Guava的RangeMap

2.1. Maven依赖

首先,在pom.xml文件中添加Google Guava库的依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

最新的依赖版本可以在这里查看。

3. 创建

创建RangeMap实例的方式有几种:

  • 使用TreeRangeMap类的create方法创建可变映射:
RangeMap<Integer, String> experienceRangeDesignationMap
  = TreeRangeMap.create();
  • 如果我们打算创建一个不可变的范围映射,可以使用ImmutableRangeMap类(遵循构建者模式):
RangeMap<Integer, String> experienceRangeDesignationMap
  = new ImmutableRangeMap.<Integer, String>builder()
  .put(Range.closed(0, 2), "Associate")
  .build();

4. 使用

让我们从一个简单的示例开始,展示如何使用RangeMap

4.1. 根据区间内的输入获取值

我们可以根据整数区间获取关联的值:

@Test
public void givenRangeMap_whenQueryWithinRange_returnsSucessfully() {
    RangeMap<Integer, String> experienceRangeDesignationMap 
     = TreeRangeMap.create();

    experienceRangeDesignationMap.put(
      Range.closed(0, 2), "Associate");
    experienceRangeDesignationMap.put(
      Range.closed(3, 5), "Senior Associate");
    experienceRangeDesignationMap.put(
      Range.closed(6, 8),  "Vice President");
    experienceRangeDesignationMap.put(
      Range.closed(9, 15), "Executive Director");

    assertEquals("Vice President", 
      experienceRangeDesignationMap.get(6));
    assertEquals("Executive Director", 
      experienceRangeDesignationMap.get(15));
}

注意:

  • Range类的closed方法假定整数区间的范围是0到2(包括两者)。
  • 上述示例中的Range包含整数。我们可以使用任何类型的区间,只要它们实现了Comparable接口,如StringCharacter、浮点数等。
  • 当尝试获取地图中不存在的区间值时,RangeMap返回null
  • 对于不可变的ImmutableRangeMap,一个键的区间不能与需要插入的键的区间重叠。如果发生这种情况,会抛出IllegalArgumentException
  • RangeMap中的键和值都不能为null。如果其中任何一个为null,将抛出NullPointerException

4.2. 根据区间移除值

接下来,我们来看看如何移除值。这里演示如何根据整个区间或部分键区间移除值:

@Test
public void givenRangeMap_whenRemoveRangeIsCalled_removesSucessfully() {
    RangeMap<Integer, String> experienceRangeDesignationMap 
      = TreeRangeMap.create();

    experienceRangeDesignationMap.put(
      Range.closed(0, 2), "Associate");
    experienceRangeDesignationMap.put(
      Range.closed(3, 5), "Senior Associate");
    experienceRangeDesignationMap.put(
      Range.closed(6, 8), "Vice President");
    experienceRangeDesignationMap.put(
      Range.closed(9, 15), "Executive Director");
 
    experienceRangeDesignationMap.remove(Range.closed(9, 15));
    experienceRangeDesignationMap.remove(Range.closed(1, 4));
  
    assertNull(experienceRangeDesignationMap.get(9));
    assertEquals("Associate", 
      experienceRangeDesignationMap.get(0));
    assertEquals("Senior Associate", 
      experienceRangeDesignationMap.get(5));
    assertNull(experienceRangeDesignationMap.get(1));
}

可以看到,即使部分移除了区间内的值,只要区间有效,我们仍然可以获取到剩余的值。

4.3. 区间映射的跨度

如果我们想知道RangeMap的整体跨度,可以使用span方法:

@Test
public void givenRangeMap_whenSpanIsCalled_returnsSucessfully() {
    RangeMap<Integer, String> experienceRangeDesignationMap = TreeRangeMap.create();
    experienceRangeDesignationMap.put(Range.closed(0, 2), "Associate");
    experienceRangeDesignationMap.put(Range.closed(3, 5), "Senior Associate");
    experienceRangeDesignationMap.put(Range.closed(6, 8), "Vice President");
    experienceRangeDesignationMap.put(Range.closed(9, 15), "Executive Director");
    experienceRangeDesignationMap.put(Range.closed(16, 30), "Managing Director");
    Range<Integer> experienceSpan = experienceRangeDesignationMap.span();

    assertEquals(0, experienceSpan.lowerEndpoint().intValue());
    assertEquals(30, experienceSpan.upperEndpoint().intValue());
}

4.4. 获取子区间映射

当我们想从RangeMap中选择一部分时,可以使用subRangeMap方法:

@Test
public void givenRangeMap_whenSubRangeMapIsCalled_returnsSubRangeSuccessfully() {
    RangeMap<Integer, String> experienceRangeDesignationMap = TreeRangeMap.create();

    experienceRangeDesignationMap
      .put(Range.closed(0, 2), "Associate");
    experienceRangeDesignationMap
      .put(Range.closed(3, 5), "Senior Associate");
    experienceRangeDesignationMap
      .put(Range.closed(6, 8), "Vice President");
    experienceRangeDesignationMap
      .put(Range.closed(8, 15), "Executive Director");
    experienceRangeDesignationMap
      .put(Range.closed(16, 30), "Managing Director");
    RangeMap<Integer, String> experiencedSubRangeDesignationMap
      = experienceRangeDesignationMap.subRangeMap(Range.closed(4, 14));

    assertNull(experiencedSubRangeDesignationMap.get(3));
    assertTrue(experiencedSubRangeDesignationMap.asMapOfRanges().values()
      .containsAll(Arrays.asList("Executive Director", "Vice President", "Executive Director")));
}

此方法返回给定Range参数与RangeMap的交集。

4.5. 获取条目

最后,如果我们想要从RangeMap中获取条目,可以使用getEntry方法:

@Test
public void givenRangeMap_whenGetEntryIsCalled_returnsEntrySucessfully() {
    RangeMap<Integer, String> experienceRangeDesignationMap 
      = TreeRangeMap.create();

    experienceRangeDesignationMap.put(
      Range.closed(0, 2), "Associate");
    experienceRangeDesignationMap.put(
      Range.closed(3, 5), "Senior Associate");
    experienceRangeDesignationMap.put(
      Range.closed(6, 8), "Vice President");
    experienceRangeDesignationMap.put(
      Range.closed(9, 15), "Executive Director");
    Map.Entry<Range<Integer>, String> experienceEntry 
      = experienceRangeDesignationMap.getEntry(10);
       
    assertEquals(Range.closed(9, 15), experienceEntry.getKey());
    assertEquals("Executive Director", experienceEntry.getValue());
}

5. 总结

在这篇教程中,我们展示了在Guava库中使用RangeMap的一些例子。它主要用于根据指定的键从映射中获取值。

这些示例的实现可以在GitHub项目中找到——这是一个基于Maven的项目,可以直接导入并运行。


» 下一篇: Java TreeMap用法