在显示进度时对大型集合进行排序

时间:2010-10-18 01:47:28

标签: java sorting progress-bar

更新进度条时对集合进行排序的最佳方法是什么?目前我的代码如下:

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的解决方案,因为它非常干净,并允许我轻松添加多线程排序算法。

7 个答案:

答案 0 :(得分:10)

你想在这里小心。您已选择使用逐步构建已排序数据结构的算法,以便(我接受)您可以显示进度条。但是,在执行此操作时,可能选择的排序方法明显慢于最佳排序。 (两种排序都是O(NlogN)但是性能要比大O行为更多......)

如果您担心这可能是个问题,请使用TreeMapCollections.sort比较对典型集合进行排序的时间。后者的工作原理是将输入集合复制到数组中,对数组进行排序,然后将其复制回来。 (效果最好 如果输入集合是ArrayList。如果您不需要将结果作为可变集合,则可以使用Collection.toArrayArrays.sortArrays.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对象中的哪个是有问题的

  1. 如果进度条从一开始就很慢,那就是items;尝试使用不同的容器,例如tree-ish或hash-y
  2. 如果进度条越接近100%越来越慢,那就是sortedItems;与上述相同的建议
  3. 请注意,导致速度减慢的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万。

这只是一个例子,相应地调整值。