为什么Java中没有SortedList?


502

在Java中,有SortedSetSortedMap接口。两者都属于Java Collections框架,并且提供了一种访问元素的排序方式。

但是,据我了解SortedList,Java 没有。您可以java.util.Collections.sort()用来对列表进行排序。

知道为什么要这样设计吗?



6
那么在列表中间插入元素时,您的预期结果是什么?
bestsss 2012年

4
@bestsss完全有可能实现不实现java.util.List接口的SortedList类。阅读该问题作为查询,以了解为什么没有支持所请求功能的数据结构。不要被诸如命名之类的无关紧要的事情分散注意力。
Alderath 2012年

@Alderath,通常的结构是带有额外的prev / next链接的树(红色/黑色,avl或btree)以支持排序。我确实使用类似结构的红色/黑色w /上一个/下一个链接。不过,这是一个相当利基的用途。该树可以有序和插入顺序遍历,具有O(logn)查找/包含但get(int)为O(n)。鉴于其利基性的适用性这一事实,我认为如果需要的话,让开发商来实施它。
bestsss 2012年

3
没有回答“为什么”的问题,但是TreeSet 的最简单的解决方法是使用一个从不返回零的Comparator,例如,int diff = this.score - that.score; return (diff == 0) ? 1 : diff; 由于它是一个臭味,我将其作为匿名构造函数参数来提供,而不是提供任何实现Comparable。
6

Answers:


682

列表迭代器首先确保您以列表的内部顺序(也称为插入顺序)获取列表的元素。更具体地说,它是按照插入元素的顺序或操作列表的方式进行的。排序可以看作是对数据结构的一种操作,有几种方法可以对列表进行排序。

我将按照自己的见解实用性的顺序进行排序:

1.考虑改用SetBag集合

注意:我将此选项放在顶部,因为这通常是您通常要执行的操作。

排序集会在插入时自动对集合进行排序,这意味着在您将元素添加到集合时会进行排序。这也意味着您无需手动对其进行排序。

此外,如果您确定不必担心(或拥有)重复的元素,则可以使用TreeSet<T>代替。它实现SortedSetNavigableSet接口并按照您可能希望从列表中进行的方式工作:

TreeSet<String> set = new TreeSet<String>();
set.add("lol");
set.add("cat");
// automatically sorts natural order when adding

for (String s : set) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

如果您不希望自然排序,则可以使用带有的构造函数参数Comparator<T>

或者,您可以使用Multisets(也称为Bags,它是Set允许重复元素的,并且存在它们的第三方实现。最明显的是在Guava库中有一个TreeMultiset,它的工作方式类似于TreeSet

2.用 Collections.sort()

如上所述,对Lists进行排序是对数据结构的一种操作。因此,对于需要以多种方式进行分类的“一个真理源”的情况,则必须手动进行分类。

您可以使用该java.util.Collections.sort()方法对列表进行排序。这是有关如何的代码示例:

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

Collections.sort(strings);
for (String s : strings) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

使用比较器

一个明显的好处是您可以Comparator在该sort方法中使用。Java还为提供了一些实现,Comparator例如,Collator对于对语言环境敏感的排序字符串很有用。这是一个例子:

Collator usCollator = Collator.getInstance(Locale.US);
usCollator.setStrength(Collator.PRIMARY); // ignores casing

Collections.sort(strings, usCollator);

在并发环境中排序

请注意,尽管sort在并发环境中使用该方法并不友好,因为将操作集合实例,因此您应考虑使用不可变的集合。这是Guava在Ordering课堂上提供的东西,并且很简单:

List<string> sorted = Ordering.natural().sortedCopy(strings);

3.用 java.util.PriorityQueue

尽管Java中没有排序列表,但是有一个排序队列可能对您同样有效。这是java.util.PriorityQueue上课。

Nico Haase在评论中链接到一个相关问题,该问题也得到了回答。

在排序的集合中,您很可能不想操纵内部数据结构,这就是为什么PriorityQueue不实现List接口的原因(因为这将使您直接访问其元素)。

警告PriorityQueue迭代器

PriorityQueue类实现Iterable<E>Collection<E>接口,因此它可以重复如常。但是,不能保证迭代器以已排序的顺序返回元素。而是(如Alderath在评论中指出的那样)您需要poll()排队直到空着。

请注意,您可以通过采用任何集合构造函数将列表转换为优先级队列:

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

PriorityQueue<String> sortedStrings = new PriorityQueue(strings);
while(!sortedStrings.isEmpty()) {
    System.out.println(sortedStrings.poll());
}
// Prints out "cat" and "lol"

4.编写自己的SortedList课程

注意:您不必这样做。

您可以编写自己的List类,该类在每次添加新元素时进行排序。取决于您的实现这可能会使计算工作变得繁重,并且毫无意义,除非您希望将其作为练习,这有两个主要原因:

  1. 它破坏了List<E>接口所具有的约定,因为add方法应确保该元素将驻留在用户指定的索引中。
  2. 为什么要重新发明轮子?如上第一点所指出的,您应该使用TreeSet或Multisets。

但是,如果您想作为练习来做,这里是一个入门的代码示例,它使用AbstractList抽象类:

public class SortedList<E> extends AbstractList<E> {

    private ArrayList<E> internalList = new ArrayList<E>();

    // Note that add(E e) in AbstractList is calling this one
    @Override 
    public void add(int position, E e) {
        internalList.add(e);
        Collections.sort(internalList, null);
    }

    @Override
    public E get(int i) {
        return internalList.get(i);
    }

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

}

请注意,如果您没有重写所需的方法,则from的默认实现AbstractList将抛出UnsupportedOperationException


12
+1。Imo,这比票数最高的答案更具建设性。它有两个小的缺点。PriorityQueue不支持随机访问。您不能执行peek(elementIndex)。所以你不能做Integer maxVal = prioQueue.peek(prioQueue.size() - 1);。其次,如果您打算仅将PriorityQueue用作排序列表,则PriorityQueue在代码中看到它比在SortedList存在这样的数据结构时要直观得多。
Alderath 2012年

12
并且,在查看了有人在评论中链接的另一个问题之后,另一个很大的缺点是,不能保证PriorityQueue的迭代器以任何特定的顺序返回元素。因此,除非我忽略了某些事情,否则例如要打印PriorityQueue中的所有对象的唯一方法就是反复轮询()队列直到其为空。对我来说,这让边界变得迟钝了。要两次打印PriorityQueue中的对象,您首先必须复制PriorityQueue,然后对原始PriorityQueue中的所有对象进行poll(),然后对副本中的所有对象进行pol()l。
Alderath 2012年

1
嗯...看来你是对的Alderath。您不能使用PriorityQueue的迭代器按预期顺序获取元素。看来我必须编辑答案。
Spoike 2012年

7
优先级队列只是一个堆,您只能访问顶部,imo它不属于问题的答案。
bestsss 2012年

1
还值得注意的是,Collections.sort()甚至还可以通过使用Comparator对象来定义用于排序的比较函数。
Mike'Pomax'Kamermans 2013年

71

因为列表的概念与自动排序的集合的概念不兼容。List的要点是,在调用之后list.add(7, elem)list.get(7)将返回对的调用elem。使用自动排序的列表,该元素可以以任意位置结束。


26

由于所有列表都已经按照添加项目的顺序进行了“排序”(FIFO排序),因此您可以使用以下顺序对它们进行“重新排序”:包括元素的自然排序 java.util.Collections.sort()

编辑:

列表作为数据结构的基础是插入项目的顺序。

集没有该信息。

如果要增加时间订购,请使用List。如果要按其他条件订购,请使用SortedSet


22
集不允许重复
最好的答案2012年

10
我认为这不是一个很好的答案。当然,java API中的列表确实具有特定的顺序,具体顺序取决于插入项目的时间/方式。然而,以依赖于插入方法/时间的方式排序的列表的存在并不妨碍具有以另一方式(例如,由比较器)确定顺序的其他数据结构。基本上,OP询问为什么没有一个与SortedSet等效的数据结构,但该数据结构应允许多次出现相等的元素。
Alderath 2012年

5
因此,我的后续问题将是:“ 为什么没有像SortedSet一样工作但可以包含多个相等元素的数据结构? ”(并且请不要回答“ 因为Sets只可以包含一个元素 ”)
Alderath

@Alderath:参见stackoverflow.com/questions/416266/sorted-collection-in-java。简而言之:使用番石榴的TreeMultiset
Janus Troelsen,

4
即使您将“已排序”放在双引号中,列表也不是“已排序”的。他们被命令。
Koray Tugay

20

Set和Map是非线性数据结构。列表是线性数据结构。

在此处输入图片说明


树数据结构SortedSetSortedMap接口 分别使用TreeSetTreeMap使用过的Red-Black树实现算法来实现。因此,请确保没有重复的项目(如果为,则为键Map)。

  • List 已经维护了一个有序的集合和基于索引的数据结构,树不是基于索引的数据结构。
  • Tree 根据定义,不能包含重复项。
  • 因为List我们可以有重复项,所以没有TreeList(即no SortedList)。
  • 列表按插入顺序维护元素。因此,如果要对列表进行排序,则必须使用java.util.Collections.sort()。根据其元素的自然顺序,它将指定列表按升序排序。

14

JavaFX SortedList

尽管花了一段时间,但Java 8确实有一些排序Listhttp://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html

如您在javadocs中所见,它是JavaFX的一部分集合的,旨在在ObservableList上提供排序视图。

更新:请注意,对于Java 11,JavaFX工具箱已移至JDK之外,现在是一个单独的库。JavaFX 11可作为可下载的SDK或从MavenCentral获得。参见https://openjfx.io


2
不幸的是,此SortedList不能像通常的列表那样工作-例如,它没有默认的构造函数(无论什么意思,您都必须使用ObservableList来构造它)
Erel Segal-Halevi

JavaFX中的SortedList显然是供GUI组件使用的,并且由于开销不适合仅拥有一个排序对象列表。即使项目中未使用GUI,也将引用整个FX模块。
COBRA.cH

1
@ COBRA.cH是的,是的。性能更好的Sorted List可能是TreeMap的薄包装,其中使用整数来计算键的重复项。您还可以将TreeSet与不返回0的比较器一起使用
。– swpalmer

为什么要下票?这个问题首先假设JDK库中没有排序列表。该答案纠正了这一假设。无论您喜欢还是不喜欢此特定排序列表的实现,都不是不赞成投票的理由。该答案不推荐该类,仅指出其存在。
罗勒·布尔克

10

对于任何新手,从2015年4月开始,Android现在在支持库中都有一个SortedList类,专门用于与RecyclerView。这是关于它的博客文章


1
值得注意的是,在本文发表时,Android的SortedList缺少对RecyclerView的onItemMoved()功能的支持。编写效率较低的我自己的SortedList是我必须克服的限制。
马修

4

另一点是插入操作的时间复杂度。对于列表插入,人们期望O(1)的复杂度。但这无法通过排序列表来保证。

最重要的一点是,列表不考虑其元素。例如,您可以列出未实现equals或的事物的列表compare


您可以使用w(登录)插入/删除/查找/包含列表,但不能使用get(int)。
bestsss 2012年

3
最后一点并不是一个很好的解释。您可以使SortedSet未实现的事情成为可能Comparable。请参见此TreeSet构造函数
Alderath 2012年

1
@Alderath-也许我的措辞太弱了。但是观察发现,树的集合和键的元素至少在相等性上必须是可比较的,而列表元素则不需要。在比较器中还是在其他位置实现集和树的排序/相等关系并不重要-但您需要一个。
Ingo 2012年

列表保证O(1)插入,它们保证O(1)访问bigocheatsheet.com
马特·奎格利

1
@Betlista你是对的!我无法更新评论,但是Java List接口不能保证其方法的任何性能规范。
马特·奎格利

3

想想看这样的:List接口有类似的方法add(int index, E element)set(int index, E element)。合同规定,一旦您在位置X添加了元素,就可以在该位置找到它,除非您在该位置之前添加或删除元素。

如果任何列表实现将以不同于索引的顺序存储元素,则上述列表方法将毫无意义。


2

List API的第一行表示这是一个有序集合(也称为序列)。如果对列表进行排序,则无法维护顺序,因此Java中没有TreeList。
正如API所说,Java列表是受Sequence启发的,请参见sequence属性http://en.wikipedia.org/wiki/Sequence_(mathematics

这并不意味着您无法对列表进行排序,而是Java严格限制了他的定义,并且默认情况下不提供列表的排序版本。



2

如果您正在寻找一种对元素进行排序的方法,但又能够通过索引有效地访问它们,则可以执行以下操作:

  1. 使用随机访问列表进行存储(例如ArrayList
  2. 确保始终对其进行排序

然后添加或删除元素可以Collections.binarySearch用来获取插入/删除索引。由于列表实现了随机访问,因此可以使用确定的索引来有效地修改列表。

例:

/**
 * @deprecated
 *      Only for demonstration purposes. Implementation is incomplete and does not 
 *      handle invalid arguments.
 */
@Deprecated
public class SortingList<E extends Comparable<E>> {
    private ArrayList<E> delegate;

    public SortingList() {
        delegate = new ArrayList<>();
    }

    public void add(E e) {
        int insertionIndex = Collections.binarySearch(delegate, e);

        // < 0 if element is not in the list, see Collections.binarySearch
        if (insertionIndex < 0) {
            insertionIndex = -(insertionIndex + 1);
        }
        else {
            // Insertion index is index of existing element, to add new element 
            // behind it increase index
            insertionIndex++;
        }

        delegate.add(insertionIndex, e);
    }

    public void remove(E e) {
        int index = Collections.binarySearch(delegate, e);
        delegate.remove(index);
    }

    public E get(int index) {
        return delegate.get(index);
    }
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.