我想维持一个大小为&lt; = 10 ^ 6的有序List<Integer>
。每次添加新元素时,我都会调用Collections.sort()
方法对列表中的新元素进行排序。据我所知ArrayList
表现优于LinkedList
。但由于我会经常调用sort()
方法,因此我逐渐理解linkedList
在对列表进行排序时效果会更好,并且在ArrayList
之后将是更好的选择,因为没有像ArrayList
一样移动元素(使用array
作为基础数据结构)。任何更有效的建议。
答案 0 :(得分:17)
您可以在排序列表中使用Collections#binarySearch
来查找正确的插入点。 ArrayList可能比LinkedList表现更好,特别是对于大小的大小,但这很容易测试。
我运行了各种方法的微基准测试:在每次插入后使用排序或使用binarySearch插入正确的位置,使用ArrayList(AL)和LinkedList(LL)。我还添加了Commons TreeList和番石榴的TreeMultiset。
<强>结论强>
TreeMultiset
,但严格来说并不是列表 - 下一个最佳选择是使用ArrayList
+ binarySearch 表现最佳的代码,供参考:
@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)
在任何情况下,最简单的方法是对所有版本进行原型设计并运行一些基准测试+分析。