1. 概述

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

RangeSet是一个包含零个或多个不相连的非空范围的集合。在向可变的RangeSet中添加范围时,任何相连的范围会被合并,而空范围则被忽略。

RangeSet的基本实现是TreeRangeSet

2. Google Guava的RangeSet

让我们来看看如何使用RangeSet类。

2.1. Maven依赖

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

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

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

3. 创建

接下来,我们探索一些创建RangeSet实例的方法。

首先,我们可以使用TreeRangeSet类的create方法创建一个可变的集合:

RangeSet<Integer> numberRangeSet = TreeRangeSet.create();

如果我们已经有了集合,可以使用TreeRangeSet类的create方法,通过传递该集合来创建一个可变的集合:

List<Range<Integer>> numberList = Arrays.asList(Range.closed(0, 2));
RangeSet<Integer> numberRangeSet = TreeRangeSet.create(numberList);

最后,如果我们需要创建一个不可变的范围集,可以使用ImmutableRangeSet类(其创建遵循构建者模式):

RangeSet<Integer> numberRangeSet 
  = new ImmutableRangeSet.<Integer>builder().add(Range.closed(0, 2)).build();

4. 使用

现在,我们从一个简单的示例开始,展示RangeSet的用法。

4.1. 添加到范围

我们可以检查输入是否位于集合中任何一个范围项内的范围内:

@Test
public void givenRangeSet_whenQueryWithinRange_returnsSucessfully() {
    RangeSet<Integer> numberRangeSet = TreeRangeSet.create();

    numberRangeSet.add(Range.closed(0, 2));
    numberRangeSet.add(Range.closed(3, 5));
    numberRangeSet.add(Range.closed(6, 8));

    assertTrue(numberRangeSet.contains(1));
    assertFalse(numberRangeSet.contains(9));
}

注意:

  • Range类的closed方法假设整数范围为0到2(包括两端)
  • 上述示例中的Range包含整数。只要实现了Comparable接口的类型(如StringCharacter、浮点数等)都可以作为范围使用
  • 对于不可变的ImmutableRangeSet,集合中的范围项不能与想要添加的范围项重叠。如果发生这种情况,会抛出IllegalArgumentException
  • 将范围输入到RangeSet中不能为null。如果输入为null,将抛出NullPointerException

4.2. 删除范围

让我们看看如何从RangeSet中删除值:

@Test
public void givenRangeSet_whenRemoveRangeIsCalled_removesSucessfully() {
    RangeSet<Integer> numberRangeSet = TreeRangeSet.create();

    numberRangeSet.add(Range.closed(0, 2));
    numberRangeSet.add(Range.closed(3, 5));
    numberRangeSet.add(Range.closed(6, 8));
    numberRangeSet.add(Range.closed(9, 15));
    numberRangeSet.remove(Range.closed(3, 5));
    numberRangeSet.remove(Range.closed(7, 10));

    assertTrue(numberRangeSet.contains(1));
    assertFalse(numberRangeSet.contains(9));
    assertTrue(numberRangeSet.contains(12));
}

如图所示,删除后,我们仍然可以访问集合中剩下的任何范围项内的值。

4.3. 范围跨度

现在,让我们看看RangeSet的整体跨度:

@Test
public void givenRangeSet_whenSpanIsCalled_returnsSucessfully() {
    RangeSet<Integer> numberRangeSet = TreeRangeSet.create();

    numberRangeSet.add(Range.closed(0, 2));
    numberRangeSet.add(Range.closed(3, 5));
    numberRangeSet.add(Range.closed(6, 8));
    Range<Integer> experienceSpan = numberRangeSet.span();

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

4.4. 获取子范围

如果我们想根据给定的范围获取RangeSet的一部分,可以使用subRangeSet方法:

@Test
public void 
  givenRangeSet_whenSubRangeSetIsCalled_returnsSubRangeSucessfully() {
  
    RangeSet<Integer> numberRangeSet = TreeRangeSet.create();

    numberRangeSet.add(Range.closed(0, 2));
    numberRangeSet.add(Range.closed(3, 5));
    numberRangeSet.add(Range.closed(6, 8));
    RangeSet<Integer> numberSubRangeSet 
      = numberRangeSet.subRangeSet(Range.closed(4, 14));

    assertFalse(numberSubRangeSet.contains(3));
    assertFalse(numberSubRangeSet.contains(14));
    assertTrue(numberSubRangeSet.contains(7));
}

4.5. 补集方法

接下来,我们使用complement方法获取除RangeSet中存在的所有值之外的值:

@Test
public void givenRangeSet_whenComplementIsCalled_returnsSucessfully() {
    RangeSet<Integer> numberRangeSet = TreeRangeSet.create();

    numberRangeSet.add(Range.closed(0, 2));
    numberRangeSet.add(Range.closed(3, 5));
    numberRangeSet.add(Range.closed(6, 8));
    RangeSet<Integer> numberRangeComplementSet
      = numberRangeSet.complement();

    assertTrue(numberRangeComplementSet.contains(-1000));
    assertFalse(numberRangeComplementSet.contains(2));
    assertFalse(numberRangeComplementSet.contains(3));
    assertTrue(numberRangeComplementSet.contains(1000));
}

4.6. 与范围的交集

最后,当我们想检查RangeSet中是否存在与给定范围的部分或全部值相交的情况时,可以使用intersect方法:

@Test
public void givenRangeSet_whenIntersectsWithinRange_returnsSucessfully() {
    RangeSet<Integer> numberRangeSet = TreeRangeSet.create();

    numberRangeSet.add(Range.closed(0, 2));
    numberRangeSet.add(Range.closed(3, 10));
    numberRangeSet.add(Range.closed(15, 18));

    assertTrue(numberRangeSet.intersects(Range.closed(4, 17)));
    assertFalse(numberRangeSet.intersects(Range.closed(19, 200)));
}

5. 总结

在这篇教程中,我们通过一些示例展示了Guava库中的RangeSetRangeSet主要用于检查一个值是否落在集合中的特定范围内。

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