将PriorityQueue转换为排序数组的最佳方法

时间:2018-06-28 09:15:08

标签: java arrays sorting java-stream priority-queue

我关心从包含数千个元素的Java PriorityQueue创建排序数组的不同样式。 Java 8 docs

  

如果需要有序遍历,请考虑使用Arrays.sort(pq.toArray())。

但是,我确实喜欢流式API,所以起初我是

Something[] elems = theHeap.stream().sorted(BY_CRITERION.reversed())
                       .toArray(Something[]::new);

(其中BY_CRITERION是PriorityQueue的自定义比较器,我确实希望相反的顺序。)与以下内容相比,使用该惯用法是否有任何缺点:

Something[] elems = theHeap.toArray(new Something[0]);
Arrays.sort(elems, BY_CRITERION.reversed());

后面的代码当然似乎更直接地遵循了API doc建议,但是除此之外,它在内存方面是否真的更有效,例如分配的临时结构更少等?

我认为,流解决方案必须将流元素缓存在临时结构(数组?)中,然后对其进行排序,最后将排序后的元素复制到toArray()中分配的数组中。

当务之急是将堆元素缓存在新分配的数组中,然后对其进行排序。因此,这可能减少一个复制操作。 (还有一个数组分配。Collection.toArray(new T[size])Collection.toArray(new T[0])的讨论在这里是切线相关的。例如,有关在OpenJDK上后者更快的原因,请参见here。)

那排序效率又如何呢? Arrays.sort()的文档说

  

临时存储要求从几乎排序的输入数组的小常数到随机排序的输入数组的n / 2个对象引用都不同

Stream.sorted()的文档在这一点上保持沉默。因此,至少就可靠记录而言,命令式解决方案似乎具有优势。

但是还有什么要知道的吗?

1 个答案:

答案 0 :(得分:4)

从根本上讲,这两种变体都具有相同的功能,并且由于两者都是库预期用例中的有效解决方案,因此在选择算法或添加优化方面,没有理由使实施方案比其他方案更受欢迎。

实际上,这意味着最昂贵的操作(排序)最终会在内部使用相同的实现方法。 Stream实现的sorted(…)操作将所有元素缓冲到一个中间数组中,然后调用Arrays.sort(T[], int, int, Comparator<? super T>),它将委派与您在第一个变体中使用的方法Arrays.sort(T[], Comparator<? super T>)相同的方法,目标是内部TimSort类中的排序方法。

因此,关于Arrays.sort的时间和空间复杂度的所有说明同样适用于Stream.sort。但是,虽然存在性能差异。对于Java 10之前的OpenJDK实现,Stream无法将sorted与后续的toArray步骤融合在一起,以将结果数组直接用于排序步骤。因此,当前,Stream变体承担着从用于排序的中间数组到传递给toArray的函数创建的最终数组的最后复制步骤。但是将来的实现可能会学到这一技巧,因此,两种解决方案之间的相关性能将完全不同。