Guava中TreeRangeMap结构简析

时间:2021-06-22 20:48:05

TreeRangeMap之所以能实现按照Range进行排序,这要归功于内部NavigableMap类型的存储变量entriesByLowerBound。NavigableMap是一个接口,继承自SortedMap接口,所以TreeRangeMap具有排序功能。这些接口的使用可以查看此处总结

public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K, V> {
private final NavigableMap<Cut<K>, TreeRangeMap.RangeMapEntry<K, V>> entriesByLowerBound = Maps.newTreeMap();
...
}

public interface NavigableMap<K,V> extends SortedMap<K,V> {
...
}

public interface SortedMap<K,V> extends Map<K,V> {
...
}

从entriesByLowerBound的定义可以体会到TreeRangeMap的巧妙。这个结构以Range中lowerBound的Cut作为键,以整个RangeMapEntry作为值,很天然的实现排序。
再来看看RangeMapEntry类,RangeMapEntry继承自抽象类AbstractMapEntry,而AbstractMapEntry继承自Entry接口,浑然一体的结构。

//定义在TreeRangeMap中,
private static final class RangeMapEntry<K extends Comparable, V> extends AbstractMapEntry<Range<K>, V> {
...
}

abstract class AbstractMapEntry<K, V> implements Entry<K, V> {
...
}
/
/定义在Map.java中,最基础的记录
interface Entry<K,V> {
...
}

AbstractMapEntry用来过渡,内部并没有数据实体,实现setValue,equals,hashCode,toString方法。
将Entry设计成Map的内部接口非常合理,因为Entry针对Map具体实现时候的一条记录。同时Map是一个接口,Entry也是一个接口,定义最基础结构时内部接口的写法值得学习。

有这些结构的支撑,TreeRangeMap能够实现切割插入,以便内部Range键不重叠。先来看看put方法

    public void put(Range<K> range, V value) {
if(!range.isEmpty()) {
Preconditions.checkNotNull(value);
this.remove(range); //先截断删除
this.entriesByLowerBound.put(range.lowerBound, new TreeRangeMap.RangeMapEntry(range, value));
//然后lowerBound为键,RangeMapEntry为值,插入entriesByLowerBound中
}
}

插入的时候之所以不会重叠,是因为插入前会截断删除。再来看看remove操作:
remove操作分两个部分,第一部分是截断rangeToRemove低点所覆盖已有Range,第二部分是截断rangeToRemove高点所覆盖到已有Range,两个不分差不多,解释其中一个。
在所有if都成功的情况下,先取出严格小于rangeToRemove低点的最大低点对应的Entry(mapEntryBelowToTruncate)。这是利用NavigableMap对Range中低点存储有序进行。然后取出这个Entry的value值(mapEntryAboveToTruncate),对应的是个RangeMapEntry,mapEntryAboveToTruncate里面包含作为Key(这里的Key是RangeMap的Key)的Range(取名r)。

  • 如果r包含rangeToRemove的低点,r有一部分跟rangeToRemove重叠,需要被切割;如果不包含证明没有重叠部分,这一部分通过了。
  • 在包含的前提下,又分两种情况。

    1. 如果r包含rangeToRemove高点,那么r需要被rangeToRemove从中间拦腰截断。
    2. 如果r不包含rangeToRemove高点,那么r只是靠近低点的部分和rangeToRemove重叠,需要截断这个部分。

    截断低点部分的源码如下:

Entry mapEntryBelowToTruncate = this.entriesByLowerBound.lowerEntry(rangeToRemove.lowerBound);
if(mapEntryBelowToTruncate != null) {
TreeRangeMap.RangeMapEntry mapEntryAboveToTruncate = (TreeRangeMap.RangeMapEntry)mapEntryBelowToTruncate.getValue();
if(mapEntryAboveToTruncate.getUpperBound().compareTo(rangeToRemove.lowerBound) > 0) {
if(mapEntryAboveToTruncate.getUpperBound().compareTo(rangeToRemove.upperBound) > 0) {
this.putRangeMapEntry(rangeToRemove.upperBound, mapEntryAboveToTruncate.getUpperBound(), ((TreeRangeMap.RangeMapEntry)mapEntryBelowToTruncate.getValue()).getValue());
}
this.putRangeMapEntry(mapEntryAboveToTruncate.getLowerBound(), rangeToRemove.lowerBound, ((TreeRangeMap.RangeMapEntry)mapEntryBelowToTruncate.getValue()).getValue());
}
}

截断高点的部分类似。

(完)