❝开发中处理各种数据范围和区间是一个常见的需求。Google的Guava库为我们提供了一个强大的工具——RangeMap,用于处理这种基于范围的映射问题。
一、RangeMap介绍
RangeMap是Guava提供的一种特殊的映射结构,它将不相交、且不为空的Range(范围)映射到一个特定的值。与传统Map不同,RangeMap的键是一个范围而不是单个元素。这种映射关系使得RangeMap在处理需要根据不同的范围来确定不同的行为或结果的问题时非常有用。它建立了区间与特定值之间的映射关系。当在已有映射的区间中插入相交的新区间时,相交部分的值会被新值覆盖,同时原区间会被拆分。此外,RangeMap不提供补集操作的功能。
二、RangeMap的应用场景
重试机制: 使用RangeMap实现基于时间的重试机制。将时间范围映射到不同的重试策略上,可以灵活地控制重试行为。
阶梯式收费: 在计费系统中,RangeMap可以方便地实现阶梯式收费模型。将不同的使用量或消费金额范围映射到不同的费用上,可以简化计费逻辑。
配置管理: 根据不同的配置项来确定不同的行为。使用RangeMap管理这些配置项,可以将配置项的范围映射到对应的行为上,提高配置管理的灵活性。
三、RangeMap的核心特性
不合并相邻的映射:RangeMap从不自动合并相邻的范围,即使这些相邻的范围映射到相同的值。这意味着每个范围都是独立且不相交的。
基于红黑树的实现:TreeRangeMap是基于红黑树实现的,这种数据结构保证了RangeMap能够提供高效的范围查找和插入操作。
灵活的范围定义:RangeMap支持各种范围定义,包括开区间、闭区间、半开半闭区间等。
插入重叠区间的行为:插入一个与已保存区间发生重叠的新区间时,TreeRangeMap会采取以下行为:
切割原有区间:为了确保每个区间都是互不重叠的,TreeRangeMap会对与新区间重叠的已保存区间进行切割。这意味着原有的区间会被分割成多个小区间。 保留插入区间的完整性:切割操作会确保您正在插入的新区间保持完整,不会被分割成多个部分。 通过单个值K查询:TreeRangeMap以区间作为键来存储数据,但可通过单K来查询它。调用get方法时使用其内部的红黑树结构来高效地查找包含给定值K的区间。由于区间是排序的,并且树结构允许快速查找,因此这个过程通常是相当高效的。
四、使用RangeMap
常用方法:
使用:
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;
import java.util.Map;
public class TreeRangeMapExample {
public static void main(String[] args) {
// 创建一个空的 TreeRangeMap
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
// 向 RangeMap 添加映射关系
rangeMap.put(Range.closed(0, 4), "Low");
rangeMap.put(Range.open(5, 10), "Medium");
rangeMap.put(Range.closedOpen(10, 15), "High");
rangeMap.put(Range.greaterThan(15), "Very High");
// 使用 get 方法获取单个值对应的映射
System.out.println(rangeMap.get(2));
// 输出: Low
System.out.println(rangeMap.get(5));
// 输出: null (因为5不包含在任何区间内)
System.out.println(rangeMap.get(7));
// 输出: Medium
System.out.println(rangeMap.get(10));
// 输出: null (因为10只包含在半开半闭区间内,这里应使用get(Range<K> range)方法)
System.out.println(rangeMap.get(12));
// 输出: High
System.out.println(rangeMap.get(20));
// 输出: Very High
// 使用 get(Range<K> range) 方法获取区间对应的映射
System.out.println(rangeMap.get(Range.singleton(10))); // 输出: High
// 使用subRangeMap方法获取子 RangeMap
RangeMap<Integer, String> subRangeMap = rangeMap.subRangeMap(Range.closedOpen(6, 12));
System.out.println(subRangeMap.asMapOfRanges()); // 输出: {(6, 10) => Medium, [10, 12) => High}
// asMapOfRanges方法获取整 RangeMap的视图
System.out.println(rangeMap.asMapOfRanges());
// 使用remove方法移除映射关系
rangeMap.remove(Range.closed(0, 4));
System.out.println(rangeMap.asMapOfRanges());
// 输出移除 [0, 4] 区间后的映射关系
// 遍历 RangeMap
for (Map.Entry<Range<Integer>, String> entry : rangeMap.asMapOfRanges().entrySet()) {
System.out.println(entry.getKey() + " => " + entry.getValue());
}
}
}
get方法的行为可能会根据键K落在哪个区间而返回相应的值,或者如果没有区间包含该键则返回null。asMapOfRanges()方法返回一个视图,它展示了RangeMap中的所有映射关系。
再看下重叠区间的行为
// 创建TreeRangeMap
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
// 向 RangeMap 添加映射关系
rangeMap.put(Range.closed(0, 4), "Low");
rangeMap.put(Range.open(5, 10), "Medium");
rangeMap.put(Range.closedOpen(10, 15), "High");
rangeMap.put(Range.greaterThan(15), "Very High");
// 插入重叠区间的场景
// 插入一个与现有区间重叠的新区间 [3, 8]
rangeMap.put(Range.closedOpen(3, 8), "Overlap");
// 打印插入重叠区间后的映射关系
System.out.println(rangeMap.asMapOfRanges());
// 输出: {[0, 3]=Low, (3, 8)=Overlap, [8, 10)=Medium, [10, 15)=High, (15, ∞)=Very High}
// 使用span()方法,返回包含给定范围内所有键值的映射关系
Range<Integer> queryRange = Range.closedOpen(2, 12);
RangeMap<Integer, String> spannedRangeMap = rangeMap.span(queryRange);
System.out.println(spannedRangeMap.asMapOfRanges());
// 输出: {(2, 3]=Low, [3, 8)=Overlap, [8, 10)=Medium, [10, 12)=High}
// lowerEndpoint和upperEndpoint获取边界
System.out.println("lower endpoint of the query range: " + queryRange.lowerEndpoint());
// 输出 2
System.out.println("upper endpoint of the query range: " + queryRange.upperEndpoint());
// 输出 12
}