Java列表排序:有没有办法让列表像TreeMap一样自动排序?

时间:2011-02-04 22:36:29

标签: java list sorting collections treemap

在Java中,您可以使用项目构建ArrayList,然后调用:

Collections.sort(list, comparator);

无论如何在列表时传递比较器,创建就像使用TreeMap一样?

目标是能够将一个元素添加到列表中,而不是将其自动附加到列表的末尾,列表将根据Comparator对自身进行排序,并将新元素插入到列表中。索引由Comparator确定。因此,基本上列表可能必须对添加的每个新元素进行重新排序。

无论如何使用Comparator或其他类似手段以这种方式实现这一目标?

16 个答案:

答案 0 :(得分:47)

您可以更改ArrayList的行为

List<MyType> list = new ArrayList<MyType>() {
    public boolean add(MyType mt) {
         super.add(mt);
         Collections.sort(list, comparator);
         return true;
    }
}; 

注意:PriorityQueue不是List,如果你不关心它是什么类型的集合,最简单的方法是使用TreeSet,它就像一个TreeMap但是一个集合。 PriorityQueue的唯一优势是允许重复。

注意:求助对大型集合来说效率不高,使用二进制搜索并插入条目会更快。 (但更复杂)

编辑:很大程度上取决于你需要“清单”来做什么。我建议你为ArrayList,LinkedList,PriorityQueue,TreeSet或其他一个有序集合编写一个List包装器,并实现实际使用的方法。这样您就可以很好地理解集合的要求,并确保它能够正常运行。

EDIT(2):由于人们对使用binarySearch非常感兴趣。 ;)

List<MyType> list = new ArrayList<MyType>() {
    public boolean add(MyType mt) {
        int index = Collections.binarySearch(this, mt);
        if (index < 0) index = ~index;
        super.add(index, mt);
        return true;
    }
};

答案 1 :(得分:15)

每个人都在建议PriorityQueue。但是,重要的是要意识到,如果iterate超过PriorityQueue的内容,则的元素将按排序顺序排列。您只能保证从方法peek()poll()等获得“最小”元素。

TreeSet似乎更合适。需要注意的是,作为Set,它不能包含重复元素,并且不支持使用索引进行随机访问。

答案 2 :(得分:5)

评论

JDK中没有SortedList实现可能是一个很好的理由。我个人无法想到在JDK中进行一次自动排序的原因。

过早优化出了问题。如果列表不是经常插入的那样读取,那么你就是在无缘无故地浪费循环排序。在读取之前进行排序会更加被动并且在某个地方有boolean表示列表在读取之前需要排序或者不需要进行排序会更好。

事情是,当您使用Iteratorfor each循环遍历列表时,您才真正关心订单,因此在迭代任何代码之前调用Collections.sort()可能比尝试更高效保持列表在每次插入时始终排序。

由于重复,List存在歧义,您如何确定性地订购重复项?有SortedSet,这是有道理的,因为它的独特性。但是对List进行排序可能会因重复项的副作用和其他约束(例如制作每个对象Comparable或者我在我的代码中显示必须具有Comparator可以执行而是工作。

.add()

上排序

如果您有一些非常特殊的情况,其中自动排序List会有用,那么您可能要做的一件事是将List实施子类化并覆盖.add()到做一个传递给自定义构造函数的Collections.sort(this, comparator)。我使用LinkedList代替ArrayList是有原因的,ArrayList是一个自然插入排序顺序List。如果你想要一个经常排序的.add(),那么它也有能力List在一个索引上是非常无用的,它必须以某种方式处理,这可能不太理想。根据Javadoc;

void    add(int index, Object element)
  

在指定元素处插入   此列表中的指定位置   ( 可选操作 )。

所以它只是抛出UnSupportedOperationException是可以接受的,或者你可以忽略index并委托给.add(Object element);如果你在方法的JavaDoc中记录它。

通常当你想要大量的插入/删除和排序时,你会使用LinkedList因为使用了`List'会有更好的性能特性。

这是一个简单的例子:

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;

public class SortedList<E> extends LinkedList<E>
{
    private Comparator<E> comparator;

    public SortedList(final Comparator<E> comparator)
    {
        this.comparator = comparator;
    }

    /**
    * this ignores the index and delegates to .add() 
    * so it will be sorted into the correct place immediately.
    */
    @Override
    public void add(int index, Object element)
    {
        this.add(element);     
    }

    @Override
    public boolean add(final E e)
    {
        final boolean result = super.add(e);
        Collections.sort(this, this.comparator);
        return result;
    }
}

最有效的解决方案:

或者你只能在获得Iterator时进行排序,如果排序顺序在迭代List时非常重要,那么这将更加注重性能。这将涵盖客户端代码的用例,在每次迭代之前不必调用Collections.sort()并将该行为封装到类中。

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;

public class SortedList<E> extends LinkedList<E>
{
    private Comparator<E> comparator;

    public SortedList(final Comparator<E> comparator)
    {
        this.comparator = comparator;
    }

    @Override
    public Iterator<E> iterator()
    {
        Collections.sort(this, this.comparator);
        return super.iterator();
    }
}

当然需要进行错误检查和处理以查看Comparator是否null以及如果是这样的话该怎么办,但这会让你有所了解。你仍然没有任何确定的方法来处理重复。

番石榴溶液:

如果您使用的是番石榴,而您应该使用

Ordering.immutableSortedCopy()只有当你需要迭代并完成它时才会完成。

答案 3 :(得分:4)

可以使用TreeSet(或者需要重复的TreeMultiset)以及更高效的随机访问,但我怀疑它是用Java实现的。使树的每个节点都记住其左子树的大小允许按时间O(log(size))索引访问元素,这是不错的。

为了实现它,您需要重写底层TreeMap的大部分内容。

答案 4 :(得分:2)

commons-collections有TreeBag

最初我建议PriorityQueue,但它的迭代顺序是未定义的,所以它没用,除非你通过获取队列克隆的头部来迭代它,直到它变空。

由于您很可能关注迭代顺序,我相信您可以覆盖iterator()方法:

public class OrderedIterationList<E> extends ArrayList<E> {
    @Override
    public Iterator<E> iterator() {
        Object[] array = this.toArray(); // O(1)
        Arrays.sort(array);
        return Arrays.asList(array).iterator(); // asList - O(1)
    }
}

您可以通过存储已排序集合的快照来改进此功能,并使用modCount验证集合是否未更改。

根据使用情况,这可能比彼得的建议更少或更有效。例如,如果添加多个项目并进行迭代。 (不在迭代之间添加项目),这可能会更有效。

答案 5 :(得分:2)

我会使用Guava TreeMultiset,假设您需要List,因为您可能有重复的元素。它会做你想要的一切。它没有的一件事是基于索引的访问,这没有多大意义,因为你没有把元素放在你选择的索引上。另一件需要注意的是,它实际上不会存储equal个对象的重复项...只是它们总数的计数。

答案 6 :(得分:1)

添加/ indexOf / remove / get元素的任何排序结构的时间少于O(n)的唯一方法是使用树。在这种情况下,操作通常具有O(log2n)并且遍历类似于O(1)。

O(n)只是一个链表。


编辑:插入链接列表w /二分查找。对于插入操作,不使用二进制结构,而不是小尺寸,这应该是最佳的。

@Peter: 有algo w / O(log2n)比较(很慢)插入和O(n)移动。 如果你需要覆盖LinkedList,那就这样吧。但这就像它能得到的一样整洁。我保持算法尽可能干净,易于理解,可以稍微优化一下。

package t1;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;

public class SortedList {


    private static <T> int binarySearch(ListIterator<? extends Comparable<? super T>> i, T key){
        int low = 0;
        int high= i.previousIndex();
        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid;
        }
        return -(low + 1);  // key not found
    }

    private static <T> T get(ListIterator<? extends T> i, int index) {
        T obj = null;
        int pos = i.nextIndex();
        if (pos <= index) {
            do {
                obj = i.next();
            } while (pos++ < index);
        } else {
            do {
                obj = i.previous();
            } while (--pos > index);
        }
        return obj;
    }
    private static void move(ListIterator<?> i, int index) {        
        int pos = i.nextIndex();
        if (pos==index)
            return;

        if (pos < index) {
            do {
                i.next();
            } while (++pos < index);
        } 
        else {
            do {
                i.previous();
            } while (--pos > index);
        }
    }
    @SuppressWarnings("unchecked")
    static  <T> int insert(List<? extends Comparable<? super T>> list, T key){
        ListIterator<? extends Comparable<? super T>> i= list.listIterator(list.size());
        int idx = binarySearch(i, key); 
        if (idx<0){
            idx=~idx;
        }
        move(i, idx);
        ((ListIterator<T>)i).add(key);
        return i.nextIndex()-1;
    }

    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        LinkedList<Integer> unsorted = new LinkedList<Integer>();
        Random r =new Random(11);
        for (int i=0;i<33;i++){
            Integer n = r.nextInt(17);
            insert(list, n);
            unsorted.add(n);            
        }

        System.out.println("  sorted: "+list);
        System.out.println("unsorted: "+unsorted);
    }

答案 7 :(得分:1)

显而易见的解决方案是创建自己的类来实现java.util.List接口,并将Comparator作为构造函数的参数。您可以在正确的位置使用比较器,即add方法将遍历现有项目并在正确的位置插入新项目。您将禁止调用add(int index, Object obj)等方法。

事实上,有人必须已经创建了这个...快速谷歌搜索至少会显示一个例子:

http://www.ltg.ed.ac.uk/NITE/nxt/apidoc/net/sourceforge/nite/util/SortedList.html

答案 8 :(得分:1)

SortedSet和List之间的主要区别是:

  • SortedSet以正确的顺序保留元素,但您无法通过索引真正访问特定元素。
  • List允许索引访问和元素的任意排序。它还允许将任何元素(通过索引或迭代器)更改为另一个元素,而不会更改位置。

您似乎希望融合两者:自动排序和允许(合理快速)索引访问。根据数据的大小以及索引读取或添加新元素的频率,这些是我的想法:

  • 一个包装的ArrayList,其中add方法使用ListIterator查找插入点,然后在那里插入元素。对于插入,这是O(n),对于索引访问,这是O(1)。
  • 一个包装的LinkedList,其中add方法使用ListIterator来查找插入点,然后在那里插入元素。 (对于插入来说,这仍然是O(n)(有时候因为ArrayList,有时甚至更多),以及索引访问。)
  • 修改后的二叉树,跟踪每个级别上两半的大小,从而实现索引访问。 (对于每次访问,这都是O(log n),但是需要一些额外的编程,因为它还没有包含在Java SE中。或者你找到了一些可以这样做的库。)

在任何情况下,SortedSet和List的接口和契约都不是真正兼容的,因此您希望List部分是只读的(或只读和删除),不允许设置和添加,以及有一个额外的对象(可能实现Collection接口)来添加对象。

答案 9 :(得分:1)

考虑我在遇到类似问题时创建的indexed-tree-map,您将能够按索引访问元素并获取元素索引,同时保持排序顺序。可以将重复项作为相同键下的值放入数组中。

答案 10 :(得分:1)

在JavaFX TransformationList层次结构中,有一个称为SortedList的东西。该列表是完全可观察到的,因此添加/删除将通知其他正在观看该列表的听众。

执行此操作的基本方法是观察另一个ObservableList的更改,并策略性地使用 Collections.binarySearch(),因为其他人建议在Olog(n)时间中定位添加或删除的索引

这里没有提到我遇到的一个问题,那就是跟踪具有相同 compareTo 签名(即T1.compareTo(T2)== 0)的项的能力。如果排序列表(我将在下面发布自己的源代码)必须具有包装元素类型,则将其称为 Element 。这类似于JavaFX的创建者对 SortedList 所做的操作。其原因完全是由于删除操作,如果有compareTo重复项,则不可能找到原始元素。通常,在类似TreeSet的 NavigableSet 实现中,这些重复项永远不会进入Set。列表不一样。

我有一个可观察的列表库,可以将它们有效地链接在一起(非常类似于Java Streams),将结果完全传播到下游,作为链更新中的前一个来源。

类层次结构

界面

/**
 * Binds the elements of this list to the function application of each element of a
 * source observable list.
 * <p>
 * While a {@code IListContentBinding} is bound, any attempt to modify its contents
 * will result in an {@code UnsupportedOperationException}. To unbind the list, call
 * {@link #unbind() unbind}.
 *
 * @param <S> The element type of the input source list that will generate change
 *            events.
 * @param <T> The element type of this output list.
 */
public interface IListContentBinding<S, T> extends ObservableList<T>, ObservableListValue<T>, IContentBinding {... details not shown ....}

所有绑定类型(排序,区分,地图,FlatMap等)的抽象基类

/**
 * Binds the elements of this list to the function application of each element of a
 * source observable list.
 * <p>
 * While a {@code ListContentBinding} is bound, any attempt to modify its contents
 * will result in an {@code UnsupportedOperationException}. To unbind the list, call
 * {@link #unbind() unbind}.
 *
 * @param <S> The element type of the source list that will generate change events.
 * @param <T> The element type of this binding.
 */
public abstract class ListContentBinding<S, T> extends ObservableListWrapper<T>
    implements IListContentBinding<S, T> {.... details not shown ....}

排序绑定类

/**
 * A {@code ListContentBinding} implementation that generates sorted elements from a
 * source list. The comparator can be set to another {@code Comparator} function at
 * any time through the {@link #comparatorProperty() comparator} property.
 * <p>
 * Unlike the Collections {@link Collections#sort(List) list sort} or Arrays
 * {@link Arrays#sort(Object[]) array sort}, once this binding has been added to the
 * order of duplicate elements cannot be guaranteed to match the original order of
 * the source list. That is the insertion and removal mechanism do not guarantee that
 * the original order of duplicates (those items where T1.compareTo(T2) == 0) is
 * preserved. However, any removal from the source list is <i>guaranteed</i> to
 * remove the exact object from this sorted list. This is because an int <i>ID</i> field
 * is added to the wrapped item through the {@link Element} class to ensure that
 * matching duplicates can be further compared.
 * <p>
 * Added/Removed objects from the source list are placed inside this sorted list
 * through the {@link Arrays#binarySearch(Object[], Object, Comparator) array binary
 * search} algorithm. For any duplicate item in the sorted list, a further check on
 * the ID of the {@code Element} corresponding to that item is compared to the
 * original, and that item. Each item added to this sorted list increases the
 * counter, the maximum number of items that should be placed in this list should be
 * no greater than {@code Integer.MAX_VALUE - Integer.MIN_VALUE}, or 4,294,967,295
 * total elements. Sizes greater than this value for an instance of this class
 * may produce unknown behavior.
 * <p>
 * Removal and additions to this list binding are proportional to <i>O(logn)</i>
 * runtime, where <i>n</i> is the current total number of elements in this sorted
 * list.
 *
 * @param <T> The element type of the source and this list binding.
 */
class ListContentSortBinding<T> extends ListContentBinding<T, T> implements IListContentSortBinding<T> {

    /**
     * Each location in the source list has a random value associated it with to deal
     * with duplicate elements that would return T1.compareTo(T2) == 0.
     */
    private Element[] elements = newElementArray(10);

    /**
     * The same elements from {@link #elements} but placed in their correct sorted
     * position according to the {@link #elementComparator element comparator}.
     */
    protected Element[] sortedElements = newElementArray(10);

    /**
     * Create a new instance.
     *
     * @param source The source observable list. Sorted elements will be generated
     *            from the source and set as the content of this list binding.
     * @param comparator The sorter. An observable that will update the comparator of
     *            this binding when invalidated. The sorter can be set to another
     *            {@code Comparator} function at anytime through the
     *            {@link #comparatorProperty() comparator} property.
     * @param options The options of this binding. Considers {@code DependencyOption}
     *            instances.
     *            <p>
     *            All bindings consider {@code BeforeChangeOption} and
     *            {@code AfterChangeOption}.
     */
    @SafeVarargs
    ListContentSortBinding(ObservableList<T> source, ObservableObjectValue<Comparator<? super T>> comparator,
        BindingOption<T, T>... options) {
        this(source, comparator.get(), options);

        comparatorProperty().bind(comparator);
    }

    /**
     * Create a new instance.
     *
     * @param source The source observable list. Sorted elements will be generated
     *            from the source and set as the content of this list binding.
     * @param comparator The sorter. The sorter can be set to another
     *            {@code Comparator} function at anytime through the
     *            {@link #comparatorProperty() comparator} property.
     * @param options The options of this binding. Considers {@code DependencyOption}
     *            instances.
     *            <p>
     *            All bindings consider {@code BeforeChangeOption} and
     *            {@code AfterChangeOption}.
     */
    @SafeVarargs
    ListContentSortBinding(ObservableList<T> source, Comparator<? super T> comparator,
        BindingOption<T, T>... options) {
        super(new ArrayList<>(), options);

        List<Observable> observables = new ArrayList<>(
            Arrays.asList(BindingOptionBuilder.extractDependencies(options)));

        setComparator(comparator);
        observables.add(comparatorProperty());

        bind(source, observables.toArray(new Observable[observables.size()]));
    }

    @Override
    protected void sourceChanged(Change<? extends T> change) {
        List<? extends T> source = change.getList();

        while (change.next()) {
            int from = change.getFrom();

            if (change.wasPermutated() || change.wasUpdated()) {
                List<? extends T> srcMod = source.subList(from, change.getTo());

                removed(source, from, srcMod.size());
                added(source, from, srcMod);
            } else {
                List<? extends T> removed = change.getRemoved();
                List<? extends T> added = change.getAddedSubList();

                if (change.wasReplaced()) {
                    int min = Math.min(added.size(), removed.size());
                    replaced(source, from, added.subList(0, min));

                    added = added.subList(min, added.size());
                    removed = removed.subList(min, removed.size());
                }

                if (removed.size() > 0) {
                    removed(source, from, removed.size());
                }

                if (added.size() > 0) {
                    if (source.size() >= elements.length) {
                        ensureSize(source.size());
                    }

                    added(source, from, added);
                }

                ensureSize(source.size());
            }
        }
    }

    /**
     * Replace the items in this sorted list binding resulting from a replacement
     * operation in the source list. For each of the items added starting at the
     * <i>from</i> index in the source list, and items was removed at the same source
     * position.
     *
     * @param source The source list.
     * @param from The index of where the replacement started in the source
     *            (inclusive). The removed and added elements occurred starting at
     *            the same source position.
     * @param added The added source elements from the change.
     */
    @SuppressWarnings({})
    private void replaced(List<? extends T> source, int from, List<? extends T> added) {
        int oldSize = size();

        for (int i = 0; i < added.size(); i++) {
            int index = from + i;
            Element e = elements[index];

            // Find the old element and remove it
            int pos = findPosition(e, index, oldSize);

            System.arraycopy(sortedElements, pos + 1, sortedElements, pos, oldSize - pos - 1);

            remove(pos);

            T t = added.get(i);

            // Create a new element and add it
            e = new Element(t);

            elements[index] = e;

            pos = findPosition(e, index, oldSize - 1);

            if (pos < 0) {
                pos = ~pos;
            }

            System.arraycopy(sortedElements, pos, sortedElements, pos + 1, oldSize - pos - 1);
            sortedElements[pos] = e;

            add(pos, t);
        }
    }

    /**
     * Add the elements from the source observable list to this binding.
     *
     * @param source The source list.
     * @param from The index of where the addition started in the source (inclusive).
     * @param added The added source elements from the change.
     */
    @SuppressWarnings({})
    private void added(List<? extends T> source, int from, List<? extends T> added) {
        if (size() == 0) {
            int size = added.size();
            Element[] temp = newElementArray(size);

            for (int i = 0; i < added.size(); i++) {
                T t = added.get(i);
                Element e = new Element(t);

                elements[i] = e;
                temp[i] = e;
            }

            if (elementComparator == null) {
                addAll(added);
                return;
            }

            Arrays.sort(temp, elementComparator);
            System.arraycopy(temp, 0, sortedElements, 0, temp.length);

            addAll(Arrays.stream(temp).map(e -> (T) e.t).collect(Collectors.toList()));

            return;
        }

        int size = size();
        System.arraycopy(elements, from, elements, from + added.size(), size - from);

        for (int i = 0; i < added.size(); i++) {
            int index = from + i;

            T t = added.get(i);
            Element e = new Element(t);

            int pos = findPosition(e, index, size);

            if (pos < 0) {
                pos = ~pos;
            }

            elements[index] = e;

            if (pos < size) {
                System.arraycopy(sortedElements, pos, sortedElements, pos + 1, size - pos);
            }

            sortedElements[pos] = e;

            add(pos, t);
            size++;
        }
    }

    /**
     * Remove the elements from this binding that were removed from the source list.
     * Update the {@link #elements} mapping.
     *
     * @param source The source list.
     * @param from The index of where the removal started in the source (inclusive).
     * @param removedSize The total number of removed elements from the source list
     *            for the change.
     */
    @SuppressWarnings({})
    private void removed(List<? extends T> source, int from, int removedSize) {
        if (source.size() == 0) {
            elements = newElementArray(10);
            sortedElements = newElementArray(10);
            elementCounter = Integer.MIN_VALUE;
            clear();
            return;
        }

        int oldSize = size();
        int size = oldSize;

        for (int i = 0; i < removedSize; i++) {
            int index = from + i;

            Element e = elements[index];

            int pos = findPosition(e, index, size);

            System.arraycopy(sortedElements, pos + 1, sortedElements, pos, size - pos - 1);

            remove(pos);
            sortedElements[--size] = null;
        }

        System.arraycopy(elements, from + removedSize, elements, from, oldSize - from - removedSize);

        for (int i = size; i < oldSize; i++) {
            elements[i] = null;
        }
    }

    /**
     * Locate the position of the element in this sorted binding by performing a
     * binary search. A binary search locates the index of the add in Olog(n) time.
     *
     * @param e The element to insert.
     * @param sourceIndex The index of the source list of the modification.
     * @param size The size of the array to search, exclusive.
     *
     * @return The position in this binding that the element should be inserted.
     */
    private int findPosition(Element e, int sourceIndex, int size) {
        if (size() == 0) {
            return 0;
        }

        int pos;

        if (elementComparator != null) {
            pos = Arrays.binarySearch(sortedElements, 0, size, e, elementComparator);
        } else {
            pos = sourceIndex;
        }

        return pos;
    }

    /**
     * Ensure that the element array is large enough to handle new elements from the
     * source list. Also shrinks the size of the array if it has become too large
     * with respect to the source list.
     *
     * @param size The minimum size of the array.
     */
    private void ensureSize(int size) {
        if (size >= elements.length) {
            int newSize = size * 3 / 2 + 1;

            Element[] replacement = newElementArray(newSize);
            System.arraycopy(elements, 0, replacement, 0, elements.length);
            elements = replacement;

            replacement = newElementArray(newSize);
            System.arraycopy(sortedElements, 0, replacement, 0, sortedElements.length);
            sortedElements = replacement;

        } else if (size < elements.length / 4) {
            int newSize = size * 3 / 2 + 1;

            Element[] replacement = newElementArray(newSize);
            System.arraycopy(elements, 0, replacement, 0, replacement.length);
            elements = replacement;

            replacement = newElementArray(newSize);
            System.arraycopy(sortedElements, 0, replacement, 0, replacement.length);
            sortedElements = replacement;
        }
    }

    /**
     * Combines the {@link #comparatorProperty() item comparator} with a secondary
     * comparison if the items are equal through the <i>compareTo</i> operation. This
     * is used to quickly find the original item when 2 or more items have the same
     * comparison.
     */
    private Comparator<Element> elementComparator;

    /**
     * @see #comparatorProperty()
     */
    private ObjectProperty<Comparator<? super T>> comparator =
        new SimpleObjectProperty<Comparator<? super T>>(this, "comparator") {
            @Override
            protected void invalidated() {
                Comparator<? super T> comp = get();

                if (comp != null) {
                    elementComparator = Comparator.nullsLast((e1, e2) -> {
                        int c = comp.compare(e1.t, e2.t);
                        return c == 0 ? Integer.compare(e1.id, e2.id) : c;
                    });
                } else {
                    elementComparator = null;
                }
            }
        };

    @Override
    public final ObjectProperty<Comparator<? super T>> comparatorProperty() {
        return comparator;
    }

    @Override
    public final Comparator<? super T> getComparator() {
        return comparatorProperty().get();
    }

    @Override
    public final void setComparator(Comparator<? super T> comparator) {
        comparatorProperty().set(comparator);
    }

    @Override
    protected void onInvalidating(ObservableList<T> source) {
        clear();
        ensureSize(source.size());
        added(source, 0, source);
    }

    /**
     * Counter starts at the Integer min value, and increments each time a new
     * element is requested. If this list becomes empty, the counter is restarted at
     * the min value.
     */
    private int elementCounter = Integer.MIN_VALUE;

    /**
     * Generate a new array of {@code Element}.
     *
     * @param size The size of the array.
     *
     * @return A new array of null Elements.
     */
    @SuppressWarnings("unchecked")
    private Element[] newElementArray(int size) {
        return new ListContentSortBinding.Element[size];
    }

包装器元素类

    /**
     * Wrapper class to further aid in comparison of two object types &lt;T>. Since
     * sorting in a list allows duplicates we must assure that when a removal occurs
     * from the source list feeding this binding that the removed element matches. To
     * do this we add an arbitrary <i>int</i> field inside this element class that
     * wraps around the original object type &lt;T>.
     */
    final class Element {
        /** Object */
        private final T t;
        /** ID helper for T type duplicates */
        private int id;

        Element(T t) {
            this.t = Objects.requireNonNull(t);
            this.id = elementCounter++;
        }

        @Override
        public String toString() {
            return t.toString() + " (" + id + ")";
        }
    }
}

JUNIT验证测试

@Test
public void testSortBinding() {
    ObservableList<IntWrapper> source = FXCollections.observableArrayList();

    int size = 100000;

    for (int i = 0; i < size / 2; i++) {
        int index = (int) (Math.random() * size / 10);
        source.add(new IntWrapper(index));
    }

    ListContentSortBinding<IntWrapper> binding =
        (ListContentSortBinding<IntWrapper>) CollectionBindings.createListBinding(source).sortElements();

    Assert.assertEquals("Sizes not equal for sorted binding | Expected: " +
        source.size() + ", Actual: " + binding.size(),
        source.size(), binding.size());

    List<IntWrapper> sourceSorted = new ArrayList<>(source);
    Collections.sort(sourceSorted);

    for (int i = 0; i < source.size(); i++) {
        IntWrapper expected = sourceSorted.get(i);
        IntWrapper actual = binding.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.value, actual.value);
    }

    System.out.println("Sorted Elements Equal: Complete.");

    // Randomly add chunks of elements at random locations in the source

    int addSize = size / 10000;

    for (int i = 0; i < size / 4; i++) {
        List<IntWrapper> added = new ArrayList<>();
        int toAdd = (int) (Math.random() * addSize);

        for (int j = 0; j < toAdd; j++) {
            int index = (int) (Math.random() * size / 10);
            added.add(new IntWrapper(index));
        }

        int atIndex = (int) (Math.random() * source.size());
        source.addAll(atIndex, added);
    }

    sourceSorted = new ArrayList<>(source);
    Collections.sort(sourceSorted);

    for (int i = 0; i < source.size(); i++) {
        IntWrapper expected = sourceSorted.get(i);
        IntWrapper actual = binding.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.value, actual.value);
    }

    System.out.println("Sorted Elements Equal - Add Multiple Elements: Complete.");

    // Remove one element at a time from the source list and compare
    // to the elements that were removed from the sorted binding
    // as a result. They should all be identical index for index.

    List<IntWrapper> sourceRemoved = new ArrayList<>();
    List<IntWrapper> bindingRemoved = new ArrayList<>();

    ListChangeListener<IntWrapper> bindingListener = change -> {
        while (change.next()) {
            if (change.wasRemoved()) {
                bindingRemoved.addAll(change.getRemoved());
            }
        }
    };

    // Watch the binding for changes after the upstream source changes

    binding.addListener(bindingListener);

    for (int i = 0; i < size / 4; i++) {
        int index = (int) (Math.random() * source.size());
        IntWrapper removed = source.remove(index);
        sourceRemoved.add(removed);
    }

    for (int i = 0; i < bindingRemoved.size(); i++) {
        IntWrapper expected = bindingRemoved.get(i);
        IntWrapper actual = sourceRemoved.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected + ", Actual: " + actual,
            expected.value, actual.value);

        Assert.assertEquals("Element refs not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.r, actual.r, 0);
    }

    System.out.println("Sorted Remove Single Element: Complete.");

    // Replace random elements from the source list

    bindingRemoved.clear();
    sourceRemoved.clear();
    int removeSize = size / 10000;

    for (int i = 0; i < size / 1000; i++) {
        int replaceIndex = (int) (Math.random() * source.size());

        int index = (int) (Math.random() * size / 10);
        IntWrapper replace = new IntWrapper(index);

        source.set(replaceIndex, replace);
    }

    sourceSorted = new ArrayList<>(source);
    Collections.sort(sourceSorted);

    for (int i = 0; i < source.size(); i++) {
        IntWrapper expected = sourceSorted.get(i);
        IntWrapper actual = binding.get(i);

        Assert.assertEquals("Elements not equal in expected sorted lists | Expected: " +
            expected.value + ", Actual: " + actual.value,
            expected.value, actual.value);
    }

    System.out.println("Sorted Elements Replace: Complete.");

    // Remove random chunks from the source list

    bindingRemoved.clear();
    sourceRemoved.clear();
    Set<IntWrapper> sourceRemovedSet =
        Collections.newSetFromMap(new IdentityHashMap<>()); // set for speed

    while (source.size() > 0) {
        int index = (int) (Math.random() * source.size());
        int toRemove = (int) (Math.random() * removeSize);
        toRemove = Math.min(toRemove, source.size() - index);

        List<IntWrapper> removed = source.subList(index, index + toRemove);
        sourceRemovedSet.addAll(new ArrayList<>(removed));

        removed.clear(); // triggers list change update to binding
    }

    Assert.assertEquals(bindingRemoved.size(), sourceRemovedSet.size());

    // The binding removed will not necessarily be placed in the same order
    // since the change listener on the binding will make sure that the final
    // order of the change from the binding is in the same order as the binding
    // element sequence. We therefore must do a contains() to test.

    for (int i = 0; i < bindingRemoved.size(); i++) {
        IntWrapper expected = bindingRemoved.get(i);

        Assert.assertTrue("Binding Removed Did Not Contain Source Removed",
            sourceRemovedSet.contains(expected));
    }

    System.out.println("Sorted Removed Multiple Elements: Complete.");
}

JUNIT基准测试

  @Test
public void sortBindingBenchmark() {
    ObservableList<IntWrapper> source = FXCollections.observableArrayList();

    ObservableList<IntWrapper> binding =
        (ListContentSortBinding<IntWrapper>) CollectionBindings.createListBinding(source).sortElements();

    int size = 200000;

    Set<IntWrapper> toAdd = new TreeSet<>();

    while (toAdd.size() < size) {
        int index = (int) (Math.random() * size * 20);
        toAdd.add(new IntWrapper(index));
    }

    // Randomize the order
    toAdd = new HashSet<>(toAdd);

    System.out.println("Sorted Binding Benchmark Setup: Complete.");

    long time = System.currentTimeMillis();

    for (IntWrapper w : toAdd) {
        source.add(w);
    }

    long bindingTime = System.currentTimeMillis() - time;

    System.out.println("Sorted Binding Time: Complete.");

    source.clear(); // clear the list and re-add

    ObservableList<IntWrapper> sortedList = new SortedList<>(source);

    time = System.currentTimeMillis();

    for (IntWrapper w : toAdd) {
        source.add(w);
    }

    long sortedListTime = System.currentTimeMillis() - time;

    System.out.println("JavaFX Sorted List Time: Complete.");

    // Make the test "fair" by adding a listener to an observable
    // set that populates the sorted set

    ObservableSet<IntWrapper> obsSet = FXCollections.observableSet(new HashSet<>());
    Set<IntWrapper> sortedSet = new TreeSet<>();

    obsSet.addListener((SetChangeListener<IntWrapper>) change -> {
        sortedSet.add(change.getElementAdded());
    });

    time = System.currentTimeMillis();

    for (IntWrapper w : toAdd) {
        obsSet.add(w);
    }

    long setTime = System.currentTimeMillis() - time;

    System.out.println("Sorted Binding Benchmark Time: Complete");

    Assert.assertEquals(sortedSet.size(), binding.size());

    System.out.println("Binding: " + bindingTime + " ms, " +
        "JavaFX Sorted List: " + sortedListTime + " ms, " +
        "TreeSet: " + setTime + " ms");
}

仅用于测试的包装器类

    /**
     * Wrapper class for testing sort bindings. Verifies that duplicates were sorted
     * and removed correctly based on the object instance.
     */
private static class IntWrapper implements Comparable<IntWrapper> {
    static int counter = Integer.MIN_VALUE;
    final int value;
    final int id;

    IntWrapper(int value) {
        this.value = value;
        this.id = counter++;
    }

答案 11 :(得分:1)

我还发现Java标准库中不存在此功能。 (但是建议在JDK团队中增加任何新的课程,祝您好运!我从来没有为此感到幸运。)

假设您的compareTo函数是一个适当的传递关系,则为此目的最快的算法(假设列表被读取的次数大约是写入的次数)是使用一种方法覆盖List.add在插入之前执行二进制搜索以查找新项目的插入点。添加元素的数量为O(log(N))。

答案 12 :(得分:0)

执行此操作的最佳方法是覆盖列表的添加实现。 我将使用LinkedList来演示它,因为它允许有效插入。

public boolean add(Integer e)
{
    int i = 0;
    for (Iterator<Integer> it = this.iterator(); it.hasNext();)
    {
        int current = it.next();
        if(current > e)
        {
            super.add(i, e);
            return true;
        }
        i++;
    }
    return super.add(e);
}

上面的代码创建了一个整数的排序列表,它总是排序的。可以轻松修改它以使用任何其他数据类型。但是在这里你必须避免使用add(index, value)函数,因为这显然会破坏排序。

虽然上面的人建议使用Arrays.sort(),但我会避免这种情况,因为它可能是一种效率显着降低的方法,尤其是因为必须在每次添加列表时调用sort方法。

答案 13 :(得分:0)

ListIterator接口的合约使它有点麻烦,但是这个方法将使用列表的单次扫描(直到插入点)执行插入:

private void add(Integer value) {
    ListIterator<Integer> listIterator = list.listIterator();

    Integer next = null;

    while (listIterator.hasNext()) {
        next = listIterator.next();

        if (next.compareTo(value) > 0) {                
            break;
        }
    }

    if (next == null || next.compareTo(value) < 0) {
        listIterator.add(value);
    } else {
        listIterator.set(value);
        listIterator.add(next);
    }
}

答案 14 :(得分:0)

SortedSet

任何SortedSet接口的实现都可以实现您期望的行为。

默认情况下,添加的对象会按照其自然顺序进行排序,即基于其Comparable::compareTo接口方法的实现。

或者,您可以通过Comparator实现来确定排序。

TreeSet

TreeSetSortedSet的常用植入。您也可以找到其他人。

重复

ListSortedSet之间的主要区别是重复,即对象相等。 List允许重复,而SortedSet与任何Set一样,不允许重复。

按索引访问

另一个差异是无法通过索引访问Set。您无法通过对象在集合中的位置编号来定位。

如果在构造SortedSet后需要这种访问权限,请创建一个List。有多种方法可以执行此操作,例如将SortedSet传递给ArrayList的构造函数。从Java 10开始,最近的一种方式是通过将List传递到SortedSet来使可解码的List.copyOf

答案 15 :(得分:-2)

我相信Priority Queue会完成这项工作。

警告(来自同一个文档页面):

  

这个类及其迭代器实现   所有的可选方法   Collection和Iterator接口。   方法中提供的Iterator   iterator()无法保证   遍历优先级的元素   以任何特定顺序排队。如果你   需要有序遍历,考虑使用   Arrays.sort(pq.toArray())