【问题标题】:Is there a generic implementation for NavigableMap subMap/headMap/tailMap?NavigableMap subMap/headMap/tailMap 是否有通用实现?
【发布时间】:2014-12-09 22:43:56
【问题描述】:

我已经实现了几次NavigableMap,但它似乎总是比应该做的工作多一点。虽然它是一个非常大的界面,但像 java.util.AbstractMap 和 Guava 的 ForwardingNavigableMap 这样的东西提供了大部分样板。然而,这两者似乎都对subMap/headMap/tailMap 的核心实现没有帮助(即除了指定包容性标志的重载之外)。例如,似乎子/尾映射的firstEntry 可以只调用ceilingEntry(minKey)higherEntry(minKey),这取决于minKey 是否包含在内。番石榴或其他东西是否提供了一种简单的方法来通用地实现这些?或者,我是否有理由忽略这种实现不实用?

【问题讨论】:

  • 只是一个问题... JDK 的NavigableMap 实现有什么问题促使您实现这个接口?
  • 它们没有问题。 :-) 在这种情况下,我正在为公开分页资源列表的 Web 服务实现客户端。客户端将在NavigableMap 中公开这些资源,该NavigableMap 会根据需要获取资源页面。

标签: java collections guava


【解决方案1】:

Guava 目前没有公开AbstractNavigableMap,但我不确定这是否会对您有很大帮助。 TBH,我不知道它会在哪里给你买太多:firstEntry 总是可以定义为Iterables.getFirst(entrySet().iterator(), null),无论你是完整的地图还是子地图或其他什么。 Guava可以想象提供subMap 和朋友每次通过higherEntry 迭代的完整实现,但与更高效的手写迭代器实现相比,这很少是您想要的。

【讨论】:

  • AbstractNavigableMap 可能没有太大帮助,但我认为可以对其进行扩展以提供一些有用的支持。对于我的 REST 客户端示例,服务器实现了按键获取、按键删除、首页、最后一页、后一页和前一页,这是您真正需要的功能 NavigableMap .当您从服务器(而不是本地数据结构)获取内容时,我认为您的效率不会更高。
【解决方案2】:

我认为可以实现我正在寻找的东西,但似乎没有现成的框架。特别是,我希望以getremove 的形式实现NavigableMap 接口,以实现任意键和firstlastnextprevious 的导航。我猜部分原因是我代表的是外部服务交互而不是内部数据结构。用例不太常见,性能预期也不同。

以下代码是我的草稿实现。它只是经过了一些测试,旨在作为概念证明。它使用 Guava 的 IteratorsOrderingForwardingNavigableMap,以及来自 Checker Framework 的空值注释。

/**
 * Abstract base implementation of {@link NavigableMap} that provides all but
 * the following methods:
 * 
 * <ul>
 * <li>{@link Map#containsKey(Object)}</li>
 * <li>{@link Map#get(Object)}</li>
 * <li>{@link Map#remove(Object)}</li>
 * <li>{@link SortedMap#comparator()}</li>
 * <li>{@link NavigableMap#lowerEntry(Object)}</li>
 * <li>{@link NavigableMap#floorEntry(Object)}</li>
 * <li>{@link NavigableMap#ceilingEntry(Object)}</li>
 * <li>{@link NavigableMap#higherEntry(Object)}</li>
 * <li>{@link NavigableMap#firstEntry()}</li>
 * <li>{@link NavigableMap#lastEntry()}</li>
 * </ul>
 * 
 * Note that {@link Collection#size()} is implemented in terms of iteration
 * using {@code firstEntry} and {@code higherEntry}, and therefore executes in
 * time proportional to the map size. Subclasses may wish to provide a more
 * efficient implementation.
 * 
 * @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 */
public abstract class AbstractNavigableMap<K, V> extends AbstractMap<K, V> implements
        NavigableMap<K, V> {

    @Override
    public abstract boolean containsKey(Object key);

    @Override
    public abstract V get(Object key);

    @Override
    public abstract V remove(Object key);

    @Override
    public K firstKey() {
        return firstEntry().getKey();
    }

    @Override
    public K lastKey() {
        return lastEntry().getKey();
    }

    @Override
    public K lowerKey(K key) {
        return lowerEntry(key).getKey();
    }

    @Override
    public K floorKey(K key) {
        return floorEntry(key).getKey();
    }

    @Override
    public K ceilingKey(K key) {
        return ceilingEntry(key).getKey();
    }

    @Override
    public K higherKey(K key) {
        return higherEntry(key).getKey();
    }

    @Override
    public Map.Entry<K, V> pollFirstEntry() {
        final Map.Entry<K, V> e = firstEntry();
        if (e != null) {
            remove(e.getKey());
        }
        return e;
    }

    @Override
    public Map.Entry<K, V> pollLastEntry() {
        final Map.Entry<K, V> e = lastEntry();
        if (e != null) {
            remove(e.getKey());
        }
        return e;
    }

    @Override
    public NavigableMap<K, V> descendingMap() {
        return new ForwardingNavigableMap<K, V>() {
            @Override
            protected NavigableMap<K, V> delegate() {
                return AbstractNavigableMap.this;
            }

            @Override
            public NavigableMap<K, V> descendingMap() {
                return new StandardDescendingMap();
            }
        }.descendingMap();
    }

    @Override
    public NavigableSet<K> navigableKeySet() {
        return new ForwardingNavigableMap<K, V>() {
            @Override
            protected NavigableMap<K, V> delegate() {
                return AbstractNavigableMap.this;
            }

            @Override
            public NavigableSet<K> navigableKeySet() {
                return new StandardNavigableKeySet();
            }
        }.navigableKeySet();
    }

    @Override
    public NavigableSet<K> descendingKeySet() {
        return descendingMap().navigableKeySet();
    }

    @Override
    public SortedMap<K, V> subMap(K fromKey, K toKey) {
        return subMap(fromKey, true, toKey, false);
    }

    @Override
    public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
        return NavigableMapSubMap.subMapOf(this, fromKey, fromInclusive, toKey, toInclusive);
    }

    @Override
    public SortedMap<K, V> headMap(K toKey) {
        return headMap(toKey, false);
    }

    @Override
    public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
        return NavigableMapSubMap.headMapOf(this, toKey, inclusive);
    }

    @Override
    public SortedMap<K, V> tailMap(K fromKey) {
        return tailMap(fromKey, true);
    }

    @Override
    public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
        return NavigableMapSubMap.tailMapOf(this, fromKey, inclusive);
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        return new NavigableMapEntrySet<K, V>(this);
    }
}

/**
 * Provides a {@link NavigableMap} view of a key range of an underlying
 * {@link NavigableMap}.
 * 
 * @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 */
class NavigableMapSubMap<K, V> extends AbstractNavigableMap<K, V> {

    private static final int UNSPECIFIED = 0;
    private static final int INCLUSIVE = 1;
    private static final int EXCLUSIVE = 2;

    private final NavigableMap<K, V> base;
    private final Comparator<? super K> comparator;
    @NullableDecl
    private final K fromKey;
    private final int fromBound;
    @NullableDecl
    private final K toKey;
    private final int toBound;

    private NavigableMapSubMap(NavigableMap<K, V> base, K fromKey, int fromBound, K toKey,
            int toBound) {
        this.base = base;
        comparator = effectiveComparator(base.comparator());
        if (fromBound != UNSPECIFIED && toBound != UNSPECIFIED &&
                comparator.compare(fromKey, toKey) > 0) {
            throw new IllegalArgumentException("fromKey > toKey");
        }
        this.fromKey = fromKey;
        this.fromBound = fromBound;
        this.toKey = toKey;
        this.toBound = toBound;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static <K> Comparator<? super K> effectiveComparator(
            @NullableDecl Comparator<? super K> comparator) {
        return comparator != null ? comparator : (Comparator) Ordering.natural();
    }

    static <K, V> NavigableMap<K, V> subMapOf(NavigableMap<K, V> base, K fromKey,
            boolean fromInclusive, K toKey, boolean toInclusive) {
        return new NavigableMapSubMap<>(base, fromKey, fromInclusive ? INCLUSIVE : EXCLUSIVE,
                toKey, toInclusive ? INCLUSIVE : EXCLUSIVE);
    }

    static <K, V> NavigableMap<K, V> headMapOf(NavigableMap<K, V> base, K toKey, boolean inclusive) {
        return new NavigableMapSubMap<>(base, null, UNSPECIFIED, toKey, inclusive ? INCLUSIVE
                : EXCLUSIVE);
    }

    static <K, V> NavigableMap<K, V> tailMapOf(NavigableMap<K, V> base, K fromKey, boolean inclusive) {
        return new NavigableMapSubMap<>(base, fromKey, inclusive ? INCLUSIVE : EXCLUSIVE, null,
                UNSPECIFIED);
    }

    @Override
    public Comparator<? super K> comparator() {
        return base.comparator();
    }

    @NullableDecl
    private boolean aboveLowerBound(@NullableDecl K key, boolean inclusive) {
        if (fromBound != UNSPECIFIED) {
            final int cmp = comparator.compare(fromKey, key);
            if (fromBound == INCLUSIVE || !inclusive ? cmp > 0 : cmp >= 0) {
                return false;
            }
        }
        return true;
    }

    @NullableDecl
    private boolean belowUpperBound(@NullableDecl K key, boolean inclusive) {
        if (toBound != UNSPECIFIED) {
            final int cmp = comparator.compare(key, toKey);
            if (toBound == INCLUSIVE || !inclusive ? cmp > 0 : cmp >= 0) {
                return false;
            }
        }
        return true;
    }

    @NullableDecl
    private boolean inBounds(@NullableDecl K key, boolean inclusive) {
        return aboveLowerBound(key, inclusive) && belowUpperBound(key, inclusive);
    }

    @NullableDecl
    private Map.Entry<K, V> lowerBound(@NullableDecl Map.Entry<K, V> e) {
        if (e != null && fromBound != UNSPECIFIED) {
            final int cmp = comparator.compare(fromKey, e.getKey());
            if (fromBound == INCLUSIVE ? cmp > 0 : cmp >= 0) {
                return null;
            }
        }
        return e;
    }

    @NullableDecl
    private Map.Entry<K, V> upperBound(@NullableDecl Map.Entry<K, V> e) {
        if (e != null && toBound != UNSPECIFIED) {
            final int cmp = comparator.compare(e.getKey(), toKey);
            if (toBound == INCLUSIVE ? cmp > 0 : cmp >= 0) {
                return null;
            }
        }
        return e;
    }

    @NullableDecl
    private Map.Entry<K, V> bound(@NullableDecl Map.Entry<K, V> e) {
        return lowerBound(upperBound(e));
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean containsKey(Object key) {
        return inBounds((K) key, true) && base.containsKey(key);
    }

    @Override
    @SuppressWarnings("unchecked")
    public V get(Object key) {
        return inBounds((K) key, true) ? base.get(key) : null;
    }

    @Override
    @SuppressWarnings("unchecked")
    public V remove(Object key) {
        return inBounds((K) key, true) ? base.remove(key) : null;
    }

    @Override
    public Map.Entry<K, V> lowerEntry(K key) {
        return bound(base.lowerEntry(key));
    }

    @Override
    public Map.Entry<K, V> floorEntry(K key) {
        return bound(base.floorEntry(key));
    }

    @Override
    public Map.Entry<K, V> ceilingEntry(K key) {
        return bound(base.floorEntry(key));
    }

    @Override
    public Map.Entry<K, V> higherEntry(K key) {
        return bound(base.higherEntry(key));
    }

    @Override
    public Map.Entry<K, V> firstEntry() {
        switch (fromBound) {
        case UNSPECIFIED:
            return base.firstEntry();
        case INCLUSIVE:
            return upperBound(base.floorEntry(fromKey));
        default:
            return upperBound(base.higherEntry(fromKey));
        }
    }

    @Override
    public Map.Entry<K, V> lastEntry() {
        switch (toBound) {
        case UNSPECIFIED:
            return base.firstEntry();
        case INCLUSIVE:
            return lowerBound(base.ceilingEntry(toKey));
        default:
            return lowerBound(base.lowerEntry(toKey));
        }
    }

    private NavigableMap<K, V> subMap(K fromKey, int fromBound, K toKey, int toBound) {
        if (fromBound == UNSPECIFIED) {
            fromKey = this.fromKey;
            fromBound = this.fromBound;
        } else if (!inBounds(fromKey, fromBound == INCLUSIVE)) {
            throw new IllegalArgumentException("fromKey out of range");
        }
        if (toBound == UNSPECIFIED) {
            toKey = this.toKey;
            toBound = this.toBound;
        } else if (!inBounds(toKey, toBound == INCLUSIVE)) {
            throw new IllegalArgumentException("toKey out of range");
        }
        return new NavigableMapSubMap<>(base, fromKey, fromBound, toKey, toBound);
    }

    @Override
    public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
        return subMap(fromKey, fromInclusive ? INCLUSIVE : EXCLUSIVE, toKey,
                toInclusive ? INCLUSIVE : EXCLUSIVE);
    }

    @Override
    public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
        return subMap(null, UNSPECIFIED, toKey, inclusive ? INCLUSIVE : EXCLUSIVE);
    }

    @Override
    public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
        return subMap(fromKey, inclusive ? INCLUSIVE : EXCLUSIVE, null, UNSPECIFIED);
    }

    @Override
    public Set<Map.Entry<K, V>> entrySet() {
        return new NavigableMapEntrySet<K, V>(this) {
            @Override
            protected Map.Entry<K, V> higherEntry(Map.Entry<K, V> from) {
                // omit unnecessary lower bound check of outer higherEntry
                return upperBound(base.higherEntry(from.getKey()));
            }
        };
    }
}

/**
 * Provides a {@link Map#entrySet()} implementation based on a
 * {@link NavigableMap} using the following methods:
 * 
 * <ul>
 * <li>{@link NavigableMap#firstEntry()}</li>
 * <li>{@link NavigableMap#higherEntry(Object)}</li>
 * <li>{@link Map#get(Object)}</li>
 * <li>{@link Map#remove(Object)}</li>
 * </ul>
 * 
 * Note that {@link Collection#size()} is implemented in terms of iteration
 * using {@code firstEntry} and {@code higherEntry}, and therefore executes in
 * time proportional to the map size. Subclasses may wish to provide a more
 * efficient implementation.
 * 
 * @param <K> the type of keys maintained by this map
 * @param <V> the type of mapped values
 */
public class NavigableMapEntrySet<K, V> extends AbstractSet<Map.Entry<K, V>> {

    private final NavigableMap<K, V> map;

    /**
     * Constructs a {@link NavigableMapEntrySet} for the given map.
     * 
     * @param map the map whose entries are being represented
     */
    public NavigableMapEntrySet(NavigableMap<K, V> map) {
        this.map = map;
    }

    /**
     * Used by {@link #iterator()} to return an entry associated with the least
     * key in this map, or {@code null} if the map is empty. The default
     * implementation simply calls {@link NavigableMap#firstEntry()}; subclasses
     * may wish to provide a more efficient implementation.
     * 
     * @return an entry with the least key, or null if the map is empty
     */
    protected Map.Entry<K, V> firstEntry() {
        return map.firstEntry();
    }

    /**
     * Used by {@link #iterator()} to return an entry associated with the least
     * key strictly greater than the key of the given entry, or {@code null} if
     * there is no such key. The default implementation simply calls
     * {@link NavigableMap#higherEntry(Object)} with the key of the given entry;
     * subclasses may wish to provide a more efficient implementation.
     * 
     * @param from the reference entry, which must have been returned from this
     *            entry set
     * @return an entry with the least key greater than the given entry, or null
     *         if there is no such key
     */
    protected Map.Entry<K, V> higherEntry(Map.Entry<K, V> from) {
        return map.higherEntry(from.getKey());
    }

    @Override
    public Iterator<Map.Entry<K, V>> iterator() {
        return new Iterator<Map.Entry<K, V>>() {
            @NullableDecl
            Map.Entry<K, V> prevEntry = null;
            @NullableDecl
            Map.Entry<K, V> nextEntry = firstEntry();

            @Override
            public boolean hasNext() {
                return nextEntry != null;
            }

            @Override
            public Map.Entry<K, V> next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                prevEntry = nextEntry;
                nextEntry = higherEntry(nextEntry);
                return prevEntry;
            }

            @Override
            public void remove() {
                if (prevEntry == null) {
                    throw new IllegalStateException();
                }
                map.remove(prevEntry.getKey());
                prevEntry = null;
            }
        };
    }

    @Override
    public int size() {
        return Iterators.size(iterator());
    }

    @Override
    public boolean contains(Object o) {
        if (o instanceof Map.Entry) {
            final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
            return Objects.equals(map.get(e.getKey()), e.getValue());
        }
        return false;
    }

    @Override
    public boolean remove(Object o) {
        if (o instanceof Map.Entry) {
            final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
            final Object key = e.getKey();
            if (Objects.equals(map.get(key), e.getValue())) {
                map.remove(key);
                return true;
            }
        }
        return false;
    }
}

【讨论】:

    【解决方案3】:
    • ForwardingNavigableMap

    如果您使用ForwardingNavigableMap,则您必须提前拥有NavigableMap,而decorator pattern 则使用decorator pattern。所以这不是你需要的。

    • 抽象地图

    AbstractMap 提供Map 的骨架实现。此类帮助您实现Map,它是NavigableMap 的接口,但没有“可导航逻辑”。无论如何,NavigableMap 是一个Map,所以AbstractMap 有助于设置NavigableMap。相比ForwardingNavigableMap,我觉得AbstractMap会是你更好的选择。

    【讨论】:

      【解决方案4】:

      我建议考虑一些更简单的东西,因为NavigableMap 是一个非常丰富的界面,您最终可能会花费大量时间在您可能不需要的功能上。即使是普通的 Map 也有大量的方法,而 AbstractMap 几乎没有帮助,因为它只提供了非常基本的实现(效率低下或抛出 UnsupportedOperationException)。

      如果您可以使用List(将sublist 用于“导航”),您将节省大量工作。无论如何,请查看 guava-testlib 以获得好的测试套件。

      如果你真的需要NavigableMap,恐怕你就不走运了。我想,没有合适的基类,因为不可能经常写出有用的东西。

      例如,似乎 sub/tail map 的 firstEntry 可能只是调用 ceilingEntry(minKey) 或 highEntry(minKey),具体取决于 minKey 是否包含在内。

      是的,但这是一个相当简单的案例。对于给定的实现,firstEntry 可能有更快的方法。

      【讨论】:

        猜你喜欢
        • 2012-12-26
        • 2011-11-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-30
        • 2011-07-21
        • 1970-01-01
        • 2017-12-27
        相关资源
        最近更新 更多