并行排序列表,而无需在 Java 8 中创建临时数组

2022-09-01 10:59:01

Java 8 提供了 java.util.Arrays.parallelSort,它使用 fork-join 框架对数组进行并行排序。但是没有相应的排序列表。Collections.parallelSort

我可以使用 ,对该数组进行排序,并将结果存储回我的列表中,但这会暂时增加内存使用量,如果我使用并行排序,这已经很高了,因为并行排序只对巨大的列表有回报。我没有使用两倍的内存(列表加上parallelSort的工作内存),而是使用三倍(列表,临时数组和parallelSort的工作内存)。(Arrays.parallelSort 文档说“该算法需要的工作空间不大于原始数组的大小”。toArray

撇开内存使用不谈,Collections.parallelSort 对于看似相当常见的操作来说也更方便。(我倾向于不直接使用数组,所以我肯定会比Arrays.parallelSort更频繁地使用它。

该库可以测试RandomAccess,以避免尝试对链接列表进行快速排序,因此这不能成为故意遗漏的理由。

如何在不创建临时数组的情况下对列表进行并行排序?


答案 1

在Java 8中似乎没有任何直接的方法可以并行排序。我不认为这在根本上是困难的。对我来说,它看起来更像是一个疏忽。List

假设的困难在于,实现对列表的实现或其内部组织一无所知。通过检查 的 Java 7 实现可以看出这一点。如您所见,它必须将列表元素复制到数组中,对它们进行排序,然后将它们复制回列表中。Collections.parallelSort(list, cmp)CollectionsCollections.sort(list, cmp)

这是扩展方法相对于 的一大优势。看起来这只是一个很小的语法优势,能够编写而不是。不同之处在于,作为接口扩展方法,可以被特定的实现覆盖。例如,使用 就地对列表进行排序,而默认实现旧的 copyout-sort-copyback 技术。List.sort(cmp)Collections.sort(list, cmp)myList.sort(cmp)Collections.sort(myList, cmp)myList.sort(cmp)ListArrayList.sort(cmp)Arrays.sort()

应该可以向接口添加一个扩展方法,该方法具有相似的语义,但并行进行排序。这将允许使用 进行直接的就地排序。(我不完全清楚默认实现应该做什么。进行 copyout-parallelSort-copyback 可能仍然是值得的。由于这将是一个API更改,因此在Java SE的下一个主要版本之前不会发生。parallelSortListList.sortArrayListArrays.parallelSort

至于Java 8解决方案,有几个解决方法,没有一个非常漂亮(这是典型的解决方法)。您可以创建自己的基于数组的实现并重写以并行排序。或者你可以子类, 覆盖 ,通过反射抓住数组并调用它。当然,您可以编写自己的实现并提供方法,但重写的优点是这适用于普通接口,您不必修改代码库中的所有代码以使用不同的子类。Listsort()ArrayListsort()elementDataparallelSort()ListparallelSort()List.sort()ListList


答案 2

我认为你注定要使用用自己的自定义实现来增强,或者改变所有其他代码来存储类型的大数据。ListparallelSortArray

这是抽象数据类型层的固有问题。它们旨在将程序员与实现的细节隔离开来。但是,当实现的细节很重要时 - 就像排序的底层存储模型一样 - 否则出色的隔离会让程序员无能为力。

标准排序文档提供了一个示例。在解释使用mergesort之后,他们说List

默认实现获取包含此列表中所有元素的数组,对数组进行排序,并循环访问此列表,从数组中的相应位置重置每个元素。(这避免了尝试就地对链接列表进行排序而导致的 n2 log(n) 性能。

换句话说,“由于我们不知道底层存储模型,如果我们知道,就无法触及它,所以我们制作了一个以已知方式组织的副本。括号中的表达式基于这样一个事实,即链表上的“i'th element accessor”是Omega(n),因此与它一起实现的正常数组合并排序将是一场灾难。事实上,在链表上有效地实现合并排序很容易。实现者只是被阻止这样做。ListListList

上的并行排序具有相同的问题。标准的顺序排序在具体实现中使用自定义 s 对其进行修复。Java人只是还没有选择去那里。也许在Java 9中。ListsortList


推荐