LinkedList与ArrayList在维护有序列表时的性能

时间:2015-01-27 06:42:20

标签: java sorting arraylist linked-list

我想维持一个大小为&lt; = 10 ^ 6的有序List<Integer>。每次添加新元素时,我都会调用Collections.sort()方法对列表中的新元素进行排序。据我所知ArrayList表现优于LinkedList。但由于我会经常调用sort()方法,因此我逐渐理解linkedList在对列表进行排序时效果会更好,并且在ArrayList之后将是更好的选择,因为没有像ArrayList一样移动元素(使用array作为基础数据结构)。任何更有效的建议。

5 个答案:

答案 0 :(得分:17)

您可以在排序列表中使用Collections#binarySearch来查找正确的插入点。 ArrayList可能比LinkedList表现更好,特别是对于大小的大小,但这很容易测试。

我运行了各种方法的微基准测试:在每次插入后使用排序或使用binarySearch插入正确的位置,使用ArrayList(AL)和LinkedList(LL)。我还添加了Commons TreeList和番石榴的TreeMultiset。

<强>结论

  • 测试者中最好的算法是使用TreeMultiset,但严格来说并不是列表 - 下一个最佳选择是使用ArrayList + binarySearch
  • ArrayList在所有情况下都比LinkedList表现更好,后者需要几分钟才能完成100,000个元素(ArrayList花费的时间不到一秒)。

表现最佳的代码,供参考:

@Benchmark public ArrayList<Integer> binarySearchAL() {
  ArrayList<Integer> list = new ArrayList<> ();

  Random r = new Random();
  for (int i = 0; i < n; i++) {
    int num = r.nextInt();
    int index = Collections.binarySearch(list, num);
    if (index >= 0) list.add(index, num);
    else list.add(-index - 1, num);
    current = list.get(0); //O(1), to make sure the sort is not optimised away
  }
  return list;
}

bitbucket上的完整代码。

完整结果

&#34;基准&#34; column包含被测方法的名称(baseLine只填充列表而不对其进行排序,其他方法有明确的名称:AL = ArrayList,LL = LinkedList,TL = Commons TreeList,treeMultiSet = guava),(n)是大小列表中,分数是以毫秒为单位的时间。

Benchmark                            (n)  Mode  Samples     Score     Error  Units
c.a.p.SO28164665.baseLine            100  avgt       10     0.002 ±   0.000  ms/op
c.a.p.SO28164665.baseLine           1000  avgt       10     0.017 ±   0.001  ms/op
c.a.p.SO28164665.baseLine           5000  avgt       10     0.086 ±   0.002  ms/op
c.a.p.SO28164665.baseLine          10000  avgt       10     0.175 ±   0.007  ms/op
c.a.p.SO28164665.binarySearchAL      100  avgt       10     0.014 ±   0.001  ms/op
c.a.p.SO28164665.binarySearchAL     1000  avgt       10     0.226 ±   0.006  ms/op
c.a.p.SO28164665.binarySearchAL     5000  avgt       10     2.413 ±   0.125  ms/op
c.a.p.SO28164665.binarySearchAL    10000  avgt       10     8.478 ±   0.523  ms/op
c.a.p.SO28164665.binarySearchLL      100  avgt       10     0.031 ±   0.000  ms/op
c.a.p.SO28164665.binarySearchLL     1000  avgt       10     3.876 ±   0.100  ms/op
c.a.p.SO28164665.binarySearchLL     5000  avgt       10   263.717 ±   6.852  ms/op
c.a.p.SO28164665.binarySearchLL    10000  avgt       10   843.436 ±  33.265  ms/op
c.a.p.SO28164665.sortAL              100  avgt       10     0.051 ±   0.002  ms/op
c.a.p.SO28164665.sortAL             1000  avgt       10     3.381 ±   0.189  ms/op
c.a.p.SO28164665.sortAL             5000  avgt       10   118.882 ±  22.030  ms/op
c.a.p.SO28164665.sortAL            10000  avgt       10   511.668 ± 171.453  ms/op
c.a.p.SO28164665.sortLL              100  avgt       10     0.082 ±   0.002  ms/op
c.a.p.SO28164665.sortLL             1000  avgt       10    13.045 ±   0.460  ms/op
c.a.p.SO28164665.sortLL             5000  avgt       10   642.593 ± 188.044  ms/op
c.a.p.SO28164665.sortLL            10000  avgt       10  1182.698 ± 159.468  ms/op
c.a.p.SO28164665.binarySearchTL      100  avgt       10    0.056 ±  0.002  ms/op
c.a.p.SO28164665.binarySearchTL     1000  avgt       10    1.083 ±  0.052  ms/op
c.a.p.SO28164665.binarySearchTL     5000  avgt       10    8.246 ±  0.329  ms/op
c.a.p.SO28164665.binarySearchTL    10000  avgt       10  735.192 ± 56.071  ms/op
c.a.p.SO28164665.treeMultiSet        100  avgt       10    0.021 ±  0.001  ms/op
c.a.p.SO28164665.treeMultiSet       1000  avgt       10    0.288 ±  0.008  ms/op
c.a.p.SO28164665.treeMultiSet       5000  avgt       10    1.809 ±  0.061  ms/op
c.a.p.SO28164665.treeMultiSet      10000  avgt       10    4.283 ±  0.214  ms/op

对于100k物品:

c.a.p.SO28164665.binarySearchAL    100000  avgt        6  890.585 ± 68.730  ms/op
c.a.p.SO28164665.treeMultiSet      100000  avgt        6  105.273 ±  9.309  ms/op

答案 1 :(得分:6)

由于java没有内置的multiset,这是适合您情况的完美数据结构,我建议使用guava库中的TreeMultiset

Multisets允许重复元素,树multiset还会增加保持集合排序的好处。

答案 2 :(得分:2)

sort()上调用LinkedList对性能造成了极大的破坏,因为List.sort()的默认实现将List转换为数组进行排序。很少有使用LinkedList的情况,即使看起来它应该有效。

如果您希望始终对集合进行排序,您应该使用有序集合,例如TreeSet或甚至PriorityQueue。它将提供更清晰的代码(以及更快的排序),因为您不必担心自己一直致电sort()

答案 3 :(得分:1)

在Oracle Java / OpenJDK 7或更高版本中,两者的渐近性能相似。 Collections.sort将列表加载到数组中,对数组进行排序,然后通过遍历它(使用ListIterator)将数组加载到列表中,替换其元素。

在这两种情况下,这是一个主要排序数组的数组排序(在OpenJDK 7及更高版本中为O(n),因为它使用了timsort),加上两个列表迭代(O(n)在这两种情况下 - 虽然我希望LinkedList有一个更糟的常数项。总的来说,这是一个O(n)进程,但LinkedList可能会更慢。

如果您批量插入元素,批量插入将总体为O(n^2),这比插入所有内容和排序要慢,或者Smac89建议使用{{1} (两者都是TreeMultiset)。

只是为了好玩,这是一种滥用O(n log(n))以允许它存储重复元素的真正糟糕方式:

TreeSet

答案 4 :(得分:1)

如果排序是您的主要考虑因素,您应该考虑使用旨在维护订单的数据结构。

使用普通的Java基类,您可以使用以下任何一种:

PriorityQueue (in case you want to retain duplicates)
TreeSet (filter duplicates)

在任何情况下,最简单的方法是对所有版本进行原型设计并运行一些基准测试+分析。