Java:排序集合允许重复,具有内存效率并提供快速插入和更新

时间:2012-10-10 20:15:02

标签: java data-structures

具体来说,我需要一个集合,它使用一个字段A进行访问,使用另一个字段(字段S)进行排序,但是接受重复的已排序集合就足够了。

我经常到这一点,我需要这个集合,而TreeMap不是一个选项,因为它不允许重复。所以现在是时候问这里了。 stackoverflow herehere指出了几种解决方法 - 即:

  • PriorityQueue :缓慢更新(删除(对象)+添加(对象))和原始键的装箱
  • Fibonacci堆:内存浪费(?)
  • TreeMap<Field_S, List<Value>> :对我来说问题是列表的内存开销和原始键的装箱
  • 排序列表或数组:问题是缓慢插入和删除 - &gt;我应该实现一个分段排序列表吗?
  • 来自guava(TreeMultimap)的
  • docs:外部依赖,可能内存效率低下(?)

任何有更好建议的人?或者我应该对自己的排序数据结构(哪一个?)起作用?其他来源(Java,开源,单元测试和小代表)也不错。


更新

目前我的用例的更多细节(虽然我上次有类似的需求)。我有一个集合(有数百万)参考,我希望能够

  • 轮询或获取有关字段S的最小元素
  • 并在字段A
  • 的帮助下更新字段S.
  • 字段S的相同值可能发生。字段A实际上是指向另一个数组的整数
  • 我想要的唯一依赖是trove4j。如果需要,我可以使用不同的mahout集合。但不是番石榴,因为虽然它是一个很好的lib,但是这些集合并没有被调整为内存效率(装箱/拆箱)。

所有对于斐波纳契堆的所有呼喊,但我担心每个元素的开销太多 - &gt;这就是我考虑使用更节省内存的“排序+分段数组”解决方案的原因。

6 个答案:

答案 0 :(得分:4)

当您需要分类收集时,您应该仔细分析您的需求 如果大多数操作是插入并且只有少数操作要使用已排序的集合进行搜索,即保持元素中的元素不断,则不是一个好的选择(由于保持元素在插入上排序的开销,这将是最常见的操作。) 在这种情况下,最好保留未排序的集合,并仅在需要时进行排序。即在搜索之前。您甚至可以使用简单的List并在需要时对其进行排序(使用Collections.sort,即mergesort)。但我谨慎地推荐这一点,因为这是有效的假设是你在处理大数据。在非常小的数据中,甚至线性搜索也足够好。

如果大多数操作是搜索那么你可以使用一个排序集合,从我的角度来看,有数据结构可供选择(你已经提到过),你可以通过基准测试看看哪一个适合您的需求。

答案 1 :(得分:3)

番石榴TreeMultiset怎么样?你要的是:一个接受重复的排序集合。但是对它的表现一无所知。

答案 2 :(得分:2)

我决定推出自己的但不是最佳解决方案只是一个TreeMap变种。如果我对这个有关内存的集合进行微调,我会保持更新。速度已经比之前的PriorityQueue尝试要好得多,因为我需要collection.remove(Object)方法(用于更新条目):

package com.graphhopper.coll;

import gnu.trove.iterator.TIntIterator;
import gnu.trove.set.hash.TIntHashSet;
import java.util.Map.Entry;
import java.util.TreeMap;

/**
 * A priority queue implemented by a treemap to allow fast key update. Or should we use a standard
 * b-tree?
 */
public class MySortedCollection {

    private int size;
    private int slidingMeanValue = 20;
    private TreeMap<Integer, TIntHashSet> map;

    public MySortedCollection(int size) {
        map = new TreeMap<Integer, TIntHashSet>();
    }

    void remove(int key, int value) {
        TIntHashSet set = map.get(value);
        if (set == null || !set.remove(key))
            throw new IllegalStateException("cannot remove key " + key + " with value " + value
                    + " - did you insert " + key + "," + value + " before?");
        size--;
        if (set.isEmpty())
            map.remove(value);
    }

    public void update(int key, int oldValue, int value) {
        remove(key, oldValue);
        insert(key, value);
    }

    public void insert(int key, int value) {
        TIntHashSet set = map.get(value);
        if (set == null)
            map.put(value, set = new TIntHashSet(slidingMeanValue));
//        else
//            slidingMeanValue = Math.max(5, (slidingMeanValue + set.size()) / 2);
        if (!set.add(key))
            throw new IllegalStateException("use update if you want to update " + key);
        size++;
    }

    public int peekValue() {
        if (size == 0)
            throw new IllegalStateException("collection is already empty!?");
        Entry<Integer, TIntHashSet> e = map.firstEntry();
        if (e.getValue().isEmpty())
            throw new IllegalStateException("internal set is already empty!?");
        return map.firstEntry().getKey();
    }

    public int peekKey() {
        if (size == 0)
            throw new IllegalStateException("collection is already empty!?");
        TIntHashSet set = map.firstEntry().getValue();
        if (set.isEmpty())
            throw new IllegalStateException("internal set is already empty!?");
        return set.iterator().next();
    }

    public int pollKey() {
        size--;
        if (size < 0)
            throw new IllegalStateException("collection is already empty!?");
        Entry<Integer, TIntHashSet> e = map.firstEntry();
        TIntHashSet set = e.getValue();
        TIntIterator iter = set.iterator();
        if (set.isEmpty())
            throw new IllegalStateException("internal set is already empty!?");
        int val = iter.next();
        iter.remove();
        if (set.isEmpty())
            map.remove(e.getKey());
        return val;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public int getSlidingMeanValue() {
        return slidingMeanValue;
    }

    @Override
    public String toString() {
        return "size " + size + " min=(" + peekKey() + "=>" + peekValue() + ")";
    }
}

答案 3 :(得分:1)

您需要决定是否需要外部依赖项。我不会为这样的事情推出自己的实现。

那就是说,你几乎没有告诉我们你使用它的原因,以及你打算用它做什么。如果没有足够的数据,我们可以告诉您这么多 - 您是否真的需要以随机顺序访问元素?你对这个系列的期望有多大?我们确实没有足够的数据来根据您的需求选择合适的数据结构。

那就是说,我会考虑一些选项。

  • ArrayListPriorityQueue,具体取决于您是否确实需要支持remove(Object)。你呢?你确定吗? (即使你确实需要支持remove(Object),如果收藏可能会很小,我会选择这个选项。)
  • 不是您关联的TreeList,而是Apache Commons Collections TreeList。尽管有名称,它实际上并没有维护排序顺序,但它的作用是支持O(log n)添加,删除和从列表中的任何位置获取。使用二进制搜索,您可以根据值的排序部分实现添加,删除或查找的O((log n)^ 2)时间。
  • 您关联的TreeList,或者 - 如果您像我一样,关心List合同 - 使用{{1}获得的自定义Guava ListMultimap }。

如果您还关心原始拳击,或者不能容忍第三方依赖,那么您将别无选择,只能编写自己的数据结构。我只是将上面的一个实现调整为你的原始类型,但这将是一个皇家的痛苦。

最后:我真的喜欢听你的用例。 Guava对这样的事情没有任何支持,因为我们没有足够的需求,或者看到一个更复杂的数据结构非常合适的用例。

答案 4 :(得分:1)

我会使用skiplist - 比树更有效的内存,允许重复,为插入和删除提供O(logn)。您甚至可以实现索引的跳转列表,它将允许您具有索引访问权限,这是树很难获得的。

答案 5 :(得分:0)