更新进度条时对集合进行排序的最佳方法是什么?目前我的代码如下:
for (int i = 0; i < items.size(); i++)
{
progressBar.setValue(i);
// Uses Collections.binarySearch:
CollectionUtils.insertInOrder(sortedItems, item.get(i));
}
这显示了进度,但随着sortedItems
中项目数量的增加,进度条减慢。有没有人有更好的方法?理想情况下,我想使用类似于Collections.sort()
的界面,以便我尝试不同的排序算法。
任何帮助都会很棒!
作为一个背景知识,这段代码从Lucene中撤回了大量文档(1-10百万个)并在它们上面运行自定义比较器。通过将数据写回磁盘来对它们进行排序将太慢而不实用。大部分成本是从磁盘上读取项目,然后在项目上运行比较器。我的电脑有大量内存,因此没有与交换到磁盘等有关的问题。
最后我选择了Stephen的解决方案,因为它非常干净,并允许我轻松添加多线程排序算法。
答案 0 :(得分:10)
你想在这里小心。您已选择使用逐步构建已排序数据结构的算法,以便(我接受)您可以显示进度条。但是,在执行此操作时,可能选择的排序方法明显慢于最佳排序。 (两种排序都是O(NlogN)
但是性能要比大O行为更多......)
如果您担心这可能是个问题,请使用TreeMap
和Collections.sort
比较对典型集合进行排序的时间。后者的工作原理是将输入集合复制到数组中,对数组进行排序,然后将其复制回来。 (效果最好
如果输入集合是ArrayList。如果您不需要将结果作为可变集合,则可以使用Collection.toArray
,Arrays.sort
和Arrays.asList
来避免最终副本。)
另一种想法是使用Comparator对象来跟踪它被调用的次数,并使用它来跟踪排序的进度。您可以利用比较器通常大约N*log(N)
次调用的事实,尽管您可能需要根据使用的实际算法 1 进行校准。
顺便提一下,计算对比较器的调用将比通过计算插入数量更好地指示进度。当您接近完成排序时,您不会看到进度速度变慢。
(您将有不同的线程读取和写入计数器,因此您需要考虑同步。将计数器声明为volatile
将起作用,但需要额外的内存流量。您也可以忽略该问题如果您对进度条感到满意,有时会显示陈旧的价值......取决于您的平台等。)
1 - 这有问题。存在一些算法,其中比较的数量可以根据被分类的数据的初始顺序而急剧变化。对于这样的算法,无法校准将在“非平均”情况下工作的计数器。
答案 1 :(得分:1)
您是否可以使用indeterminate进度条?这仍然向用户提供了一些正在发生的事情的反馈。你的代码看起来像这样:
progessbar.setIndeterminate(true);
ArrayList sorted = new ArrayList(items);
Colletions.sort(sorted);
progessBar.setString("Hey you're done!");
我认为使用内置排序可以获得更多更好的性能,而不是你正在进行的二进制插入排序。
答案 2 :(得分:1)
为什么不实现自己的合并排序(这是Collections.sort正在做的事情)并更新算法关键点的进度条(比如,每次合并超过5%的数组后)?
答案 3 :(得分:0)
如果您只是比较排序时间,请打印排序前后的时间。
预测野外种类需要多长时间才会很困难。对于某些种类,它取决于输入的顺序。我会使用i/(double) items.size()
生成已完成工作的比例,并称之为晴朗的一天。您可以选择每items.size()/100
次迭代更新一次条形图。没有理由用无用的更新来抨击糟糕的进度条。
答案 4 :(得分:0)
这里的问题是排序的物理机制 - 随着sortedItems
变大,insertInOrder
根据定义需要更长时间,因为它最有可能是O(n lg n) + O(n)
操作(使用二进制搜索)找到下一个最小的项目然后插入项目)。随着您的收藏品变得越来越大,将下一个项目插入适当的位置需要更长时间,这是不可避免的。
逼近时间线性增加的进度条的唯一方法是使用类似于lg
函数的倒数的一些近似值,因为对前1000个项目进行排序可能需要花费一些时间类似于对最后10个项目进行排序(这当然是一种概括)。
答案 5 :(得分:0)
我可能错过了一些东西,因为没有其他人提到它,但听起来像源List
对象的运行时类型不是RandomAccess的实现者,因此你的Collections.binarySearch
调用在O(n)时间运行。这会让事情变得非常缓慢,非常明显,当你需要对要排序的项目数量增加一倍时。
此外,如果您使用LinkedList
作为sortedItems
,那么插入也是O(n)。
如果是这种情况,那么当您从100万件到200万件时,您的预期时间也将大致翻倍,这是完全合理的。
要诊断2个List
对象中的哪个是有问题的
items
;尝试使用不同的容器,例如tree-ish或hash-y sortedItems
;与上述相同的建议请注意,导致速度减慢的List
可能都是{{1}}。这也与进度条无关。您描述的问题是关于排序的算法,而不是更新进度条。
答案 6 :(得分:0)
进度条上有一个简单的方法就是这个。
您可以使用mod修改更新进度的调用次数,而不考虑项目大小。例如,
public void run(int total) {
int updateInterval = total / 10;
System.out.println("interval = " + updateInterval);
for(int i = 0; i < total; i++) {
if(i % updateInterval == 0) {
printProgress((float)i / total * 100f);
}
// do task here
}
}
private void printProgress(float value) {
System.out.println(value + "%");
}
这将更新进度条10次(或9?检查边界条件)是否为10或1000万。
这只是一个例子,相应地调整值。