【问题标题】:Why is there no SortedList in Java?为什么Java中没有SortedList?
【发布时间】:2012-02-02 06:24:42
【问题描述】:

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

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

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

【问题讨论】:

  • 那么在列表中间插入元素时,您的预期结果是什么?
  • @bestsss 完全有可能拥有一个未实现 java.util.List 接口的 SortedList 类。将问题阅读为询问为什么没有支持所请求功能的数据结构。不要被命名等无关紧要的细节分心。
  • @Alderath,通常的结构是一棵树(红/黑、avl 或 btree),带有额外的 prev/next 链接以支持排序。我确实使用类似的结构红色/黑色 w/prev/next 链接。不过,这是一个非常小众的用途。树可以按顺序和插入顺序遍历,具有 O(logn) 查找/包含但 get(int) 是 O(n)。鉴于它的利基适用性,我认为它留给开发人员在需要时实施。
  • 不回答“为什么”这个问题,但是 TreeSet 的最简单的解决方法是使用永远不会返回零的比较器,例如int diff = this.score - that.score; return (diff == 0) ? 1 : diff; 因为这是一个很臭的 hack,所以我会将其作为匿名构造函数参数提供,而不是使用任何实现 Comparable。

标签: java sorting collections


【解决方案1】:

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

我会按照我个人认为的有用性来排序:

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&lt;T&gt; 的构造函数参数。

或者,您可以使用Multisets(也称为Bags,即允许重复元素的Set,而不是第三方它们的实现。最值得注意的是来自Guava libraries 有一个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"

使用比较器

一个明显的好处是您可以在sort 方法中使用Comparator。 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 类。

Cmets 中的 Nico Haase 链接到 related question,该 related question 也回答了这个问题。

在排序的集合中你很可能不想操作内部数据结构,这就是 PriorityQueue 不实现 List 接口的原因(因为这会让你直接访问它的元素) .

注意PriorityQueue 迭代器

PriorityQueue 类实现了Iterable&lt;E&gt;Collection&lt;E&gt; 接口,因此可以像往常一样对其进行迭代。但是,迭代器不能保证按排序顺序返回元素。相反(正如 Alderath 在 cmets 中指出的那样)您需要 poll() 队列直到为空。

请注意,您可以通过constructor that takes any collection 将列表转换为优先级队列:

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&lt;E&gt; 接口的约定,因为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();
    }

}

请注意,如果您没有覆盖您需要的方法,那么来自AbstractList 的默认实现将抛出UnsupportedOperationExceptions。

【讨论】:

  • +1。 Imo,这比投票最多的答案更具建设性。不过,它有两个小缺点。 PriorityQueue 不支持随机访问。你不能做 peek(elementIndex)。所以你不能做例如Integer maxVal = prioQueue.peek(prioQueue.size() - 1);。其次,如果您打算仅将 PriorityQueue 用作排序列表,那么在代码中看到 PriorityQueue 听起来不如看到 SortedList(如果存在这样的数据结构)那么直观。
  • 并且,在查看了在 cmets 中链接的另一个问题之后,另一个大缺点是 PriorityQueue 的迭代器不能保证以任何特定顺序返回元素。所以,除非我忽略了某些东西,否则唯一的方法就是打印 PriorityQueue 中的所有对象是为了重复 poll() 队列直到它为空。对我来说,这感觉有点重蹈覆辙。要打印 PriorityQueue 中的对象两次,首先必须复制 PriorityQueue,然后 poll() 原始 PriorityQueue 中的所有对象,然后 pol()l 副本中的所有对象。
  • 嗯...看起来你是对的 Alderath。您不能使用 PriorityQueue 的迭代器来按预期顺序获取元素。看来我必须编辑我的答案。
  • 优先队列只是一个堆,只能访问顶部,imo不属于问题的答案。
  • 另外值得注意的是,Collections.sort() 甚至允许您使用Comparator 对象定义用于排序的比较函数。
【解决方案2】:

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

【讨论】:

  • List 的概念意味着存在 some 顺序,并且 list.get(n) 操作将是确定性的,这意味着它将始终在位置 n 返回相同的元素只要列表没有被修改。我不同意“列表的概念”要求将其作为插入顺序。是的,List 接口确实具有 list.add(index, element) 方法,这对于排序的集合没有意义,但根据文档它是可选的。
【解决方案3】:

由于所有列表都已按添加项目的顺序“排序”(FIFO 排序),您可以使用 java.util.Collections.sort() 以其他顺序“重新排序”它们,包括元素的自然排序。

编辑:

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

套装没有该信息。

如果您想通过添加时间来订购,请使用List。如果您想按其他条件订购,请使用SortedSet

【讨论】:

  • 设置不允许重复
  • 我认为这不是一个很好的答案。当然,java API 中的列表确实有一个特定的顺序,由何时/如何插入项目来确定。但是,以取决于插入方法/时间的方式排序的列表的存在并不能阻止其他数据结构的顺序以另一种方式确定(例如,通过比较器)。基本上,OP 是在问为什么没有与 SortedSet 等效的数据结构,只是数据结构应该允许多次出现相等的元素。
  • 所以我的后续问题是:“为什么没有数据结构可以像 SortedSet 一样工作,但它可以包含多个相等的元素?”(请不要t 回答“因为集合只能包含一个元素”)
  • @Alderath:见stackoverflow.com/questions/416266/sorted-collection-in-java。简而言之:使用 Guava 的 TreeMultiset
  • 列表未“排序”,即使您将“排序”放在双引号中。它们是有序的。
【解决方案4】:

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


树形数据结构SortedSetSortedMap接口分别使用使用的Red-Black tree实现算法实现TreeSetTreeMap。因此它确保没有重复的项目(或Map 的情况下的键)。

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

【讨论】:

  • 为什么集合和映射是非线性数据结构?你可以在它们上面模拟一个数组。
【解决方案5】:

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

【讨论】:

  • 不幸的是,这个 SortedList 不像通常的列表那样工作——例如,它没有默认构造函数(你必须使用 ObservableList 来构造它,不管这意味着什么......)
  • JavaFX 中的 SortedList 显然是用于 GUI 组件的,并且由于开销不适合仅具有排序对象列表。即使项目中没有使用 GUI,这也意味着引用整个​​ FX 模块。
  • @COBRA.cH 是的,这是真的。性能更高的排序列表可能是 TreeMap 的薄包装器,其中使用整数来计算键的重复次数。您还可以使用带有从不返回 0 的比较器的 TreeSet。
  • 为什么投反对票?问题始于假设 JDK 库中没有排序列表。这个答案纠正了这个假设。无论您喜欢还是不喜欢这个特定排序列表的实现,都不是拒绝投票的理由。这个答案不推荐类,只是指出它的存在。
【解决方案6】:

对于任何新用户,截至 2015 年 4 月,Android 现在在支持库中有一个 SortedList 类,专门用于与 RecyclerView 一起使用。这是关于它的blog post

【讨论】:

  • 值得注意的是,在发表此评论时,Androids SortedList 缺乏对 RecyclerView 的 onItemMoved() 功能的支持。我必须自己编写效率较低的 SortedList 才能绕过限制。
【解决方案7】:

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

最重要的一点是列表对它们的元素没有任何假设。 例如,您可以列出未实现 equalscompare 的事物。

【讨论】:

  • 您可以使用 O(logn) 插入/删除/查找/包含列表,但不能使用 get(int)。
  • 最后一点并不是一个很好的解释。您可以为未实现 Comparable 的事物创建 SortedSet。见this TreeSet constructor
  • @Alderath - 也许我的措辞太弱了。然而,观察认为 Sets 的元素和 Trees 的键必须至少在相等性方面具有可比性,而列表元素则不需要。 Sets 和 Trees 的排序/相等关系是否在 Comparator 或其他地方实现并不重要 - 但您需要一个。
  • 列表保证O(1) 插入,它们保证O(1) 访问bigocheatsheet.com
  • @Betlista 你是对的!我无法更新我的评论,但 Java List 接口不保证其方法的任何性能规范。
【解决方案8】:

可以这样想:List 接口具有add(int index, E element)set(int index, E element) 等方法。约定是,一旦您在位置 X 添加了一个元素,您就会在那里找到它,除非您在它之前添加或删除元素。

如果任何列表实现会以某种顺序存储元素而不是基于索引,那么上述列表方法将毫无意义。

【讨论】:

    【解决方案9】:

    List API 的第一行说它是一个有序集合(也称为序列)。如果对列表进行排序,则无法保持顺序,因此 Java 中没有 TreeList。
    正如 API 所说,Java List 的灵感来自于 Sequence 并查看了序列属性 http://en.wikipedia.org/wiki/Sequence_(mathematics)

    并不是说你不能对列表进行排序,而是Java对他的定义很严格,默认不提供排序版本的列表。

    【解决方案10】:

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

    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);
        }
    }
    

    【讨论】:

      【解决方案11】:

      我认为由于以下原因,以上所有内容都不能回答这个问题,

      1. 由于可以通过使用其他集合来实现相同的功能,例如 TreeSet、Collections、PriorityQueue..etc(但这是一种替代方案,它也会施加它们的约束,即 Set 将删除重复的元素。简单地说,即使它没有施加任何约束,它没有回答为什么 SortedList 不是由 java 社区创建的问题)
      2. 由于 List 元素不实现 compare/equals 方法(这适用于 Set 和 Map 也适用于一般项目不实现 Comparable 接口但当我们需要这些项目按排序顺序并希望使用 TreeSet /TreeMap,items 应该实现 Comparable 接口)
      3. 由于 List 使用索引并且由于排序而无法正常工作(这可以通过引入中间接口/抽象类来轻松处理

      但是没有人说出它背后的确切原因,因为我相信这些问题最好由 java 社区本身来回答,因为它只有一个具体的答案,但让我尽力回答如下,

      众所周知,排序是一项昂贵的操作,List 和 Set/Map 之间存在一个基本区别,即 List 可以有重复项,而 Set/Map 则不能。 这就是为什么我们以 TreeSet/TreeMap 的形式获得 Set/Map 的默认实现的核心原因。在内部,这是一棵红黑树,每个操作(插入/删除/搜索)都具有 O(log N) 的复杂性,其中由于重复,List 无法适应此数据存储结构。

      现在问题来了,我们也可以为 List 选择一个默认的排序方法,也像 MergeSortCollections.sort(list) 方法使用,复杂度为 O(N log N)强>。社区并没有故意这样做,因为我们确实有多种排序算法可供选择,用于非不同元素的排序算法,如 QuickSort、ShellSort、RadixSort...等。未来可能会有更多。此外,有时相同的排序算法会根据要排序的数据执行不同的操作。因此,他们希望保持此选项处于打开状态,并将其留给我们选择。这不是 Set/Map 的情况,因为 O(log N) 是最好的排序复杂度。

      【讨论】:

        【解决方案12】:

        https://github.com/geniot/indexed-tree-map

        考虑使用indexed-tree-map。它是一个增强的 JDK 的 TreeSet,它提供按索引访问元素并查找元素的索引,而无需迭代或隐藏的底层列表来备份树。该算法基于每次发生变化时更新变化节点的权重。

        【讨论】:

          猜你喜欢
          • 2012-07-21
          • 2011-04-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-10
          相关资源
          最近更新 更多