Java 7 是不是对方法 Arrays.Sort 使用 Tim Sort?

Posted

技术标签:

【中文标题】Java 7 是不是对方法 Arrays.Sort 使用 Tim Sort?【英文标题】:Is Java 7 using Tim Sort for the Method Arrays.Sort?Java 7 是否对方法 Arrays.Sort 使用 Tim Sort? 【发布时间】:2011-04-30 10:05:01 【问题描述】:

我找不到Java 7 的文档,我只能找到关于Java 6 的,它仍然是快速或合并。有谁知道如何在 Java 7 中找到方法 Arrays.sort 的文档?

【问题讨论】:

它们比 Quick and Merge 更好 What is the sorting algorithm for Java的可能重复 【参考方案1】:

Java 7 对基元使用 Dual-Pivot Quicksort,对对象使用 TimSort。

根据Java 7 API doc for primitives:

实现说明:排序 算法是双枢轴快速排序 弗拉基米尔·雅罗斯拉夫斯基,乔恩·本特利, 和约书亚布洛赫。该算法 在许多方面提供 O(n log(n)) 性能 导致其他快速排序的数据集 降级为二次性能, 并且通常比 传统(单轴)快速排序 实现。

根据Java 7 API doc for objects:

实施改编自 Tim Peters 的 Python 列表排序 ( 时间排序)。它使用彼得的技术 麦克罗伊的“乐观排序和 信息论复杂性”,在 第四届年刊 ACM-SIAM 离散研讨会 算法,第 467-474 页,1993 年 1 月。

Timsort 是一种混合的“合并排序和插入排序”。

不确定这是否与 Java 6 中的有很大不同,for Arrays.sort JDK6:

改编自 Jon L. Bentley 和 M. Douglas McIlroy 的 “设计排序函数”, 软件实践与经验,卷。 23(11) P. 1249-1265(1993 年 11 月)

对于 Object[] 或集合 (Collections.sort()) 使用合并排序。

【讨论】:

啊太棒了。谢谢。对于基元,它使用双轴快速排序,对于对象,它使用 Tim Sort 知道为什么要区分对象的 timsort 和原语的双轴快速排序吗?也许是内存考虑? @Przemek:根据文档,是一样的 令人困惑的是,实现原始排序的类称为DualPivotQuicksort - 但它实际上实现了至少 4 种不同的排序算法(其中只有一种是快速排序)并使用启发式方法在它们之间进行选择。例如,byte 数组从不使用快速排序进行排序,而“结构化”数组(已经具有很大程度的排序)使用合并排序(与 timsort 有很多共同点)进行排序。 Timsort is 显然完全不同:reddit.com/r/programming/comments/9jj5z/…【参考方案2】:

是的! ...也没有。

总结

在撰写本文时(2016 年),在当前的 Open JDK 0 实现中,Tim Sort 通常用于对对象数组进行排序(即Arrays.sort(Object[]) 和朋友) - 但对于 原始数组Arrays.sort 方法的其余部分)使用了各种其他方法。

对于原语,启发式方法在多种排序方法中进行选择,例如快速排序、归并排序、计数排序3。取决于被排序的数据。这些决策中的大多数都是根据要排序的数组的类型和大小预先做出的,但对于intlong 元素,决策实际上是基于数组的测量排序的自适应的。因此,在许多情况下,您在适应/自省(TimSort 或类似的合并排序)之上还有适应/自省(选择算法的启发式方法)!

详情

Tim Sort 用于大多数类型的对象,例如 Arrays.sort(Object[] a),除非用户通过将系统属性 java.util.Arrays.useLegacyMergeSort 设置为 true 来明确请求旧版行为。

对于原语,情况更为复杂。至少从 JDK 8(版本1.8.0_111)开始,根据要排序的数组的大小、原始类型和数组的测量“排序性”,使用了各种启发式方法。这是一个概述:

对于除字节1 之外的所有基本类型,少于 47 个元素的数组使用插入排序进行简单排序(请参阅DualPivotQuicksort.INSERTION_SORT_THRESHOLD)。 在对使用合并或快速排序时出现的子数组进行排序并且子数组的大小低于阈值时使用此阈值。因此,某种形式的插入排序将用于所有排序,对于小型数组,它是唯一使用的算法。 对于基本类型byteshortchar,counting sort 用于较大的数组。这是一个简单的排序,需要 O(n + range) 时间,其中 range 是字节 (256) 或短/字符 (65536) 值的总数。排序涉及分配range 值的基础数组,因此仅当要排序的元素数量占总范围的很大一部分时才使用它。特别是,它用于超过 29 个元素的字节数组(即约 11% 的范围),以及超过 3200 个元素的短/字符数组(约 5% 的范围)。 对于字节数组,始终使用上述两种方法之一。 对于高于插入排序阈值的intlong 数组以及高于插入排序阈值和低于计数排序阈值的short/char 数组,可以使用以下两种算法之一:双主元快速排序或归并排序。使用哪一个取决于数组排序的度量:输入分为升序或降序元素的runs。如果此类运行的次数大于 66,则认为该数组大部分未排序,并使用双轴快速排序进行排序。否则,该数组被认为大部分已排序,并使用归并排序(使用已枚举的运行作为起点)。

查找运行然后使用mergesort对其进行排序的想法实际上与TimSort非常相似,尽管存在一些差异。因此,至少对于某些参数,JDK 使用了运行感知合并排序,但对于许多其他参数组合,它使用了不同的算法,总共使用了至少 5 种不同的算法!

基本原理

Object[] 与原始的不同排序行为背后的原因可能至少有两个方面:

1) Object[] 的排序必须稳定:排序相同的对象将以与输入相同的顺序出现。对于原始数组,不存在这样的概念:原始数组完全由它们的值定义,因此稳定排序和不稳定排序之间没有区别。这使得原始排序不再需要稳定的算法来支持速度。

2) 各种Object[] 需要涉及Object.compare() 方法,这可能是任意复杂和昂贵的。即使compare()方法很简单,一般也会有方法调用开销,除非整个排序方法可以内联2。因此,Object[] 的种类通常会偏向于最小化总比较,即使以一些额外的算法复杂性为代价。

另一方面,各种原始数组只是直接比较原始值,这些原始值通常采用一两个周期的顺序。在这种情况下,应该考虑比较的成本和周围的算法来优化算法,因为它们可能具有相同的量级。


0 至少对于Java 7 和Java 9 之间的版本,当然这也包括Oracle 的JDK,因为它是基于Open JDK 的。 可能其他实现使用类似的方法,但我没有检查。

1 对于字节数组,插入排序阈值实际上是 29 个元素,因为这是使用计数排序的下限。

2 这似乎不太可能,因为它相当大。

3 计数排序仅用于范围相对有限的 16 位或更少的值:byteshortchar

【讨论】:

我没有立即看到 Timsort 最终可能会在源代码中用于原始数组的位置...? @rogerdpack - 你是对的,与我现在看到的相比,我要么弄错了,要么查看了不同版本的源代码。至少在 JDK8 中,原始排序委托给 DualPivotQuicksort,它使用多种策略:通常用于“结构化”(即长期运行)数据的普通合并排序,或用于非结构化数据的快速排序,以及其他策略,如字节值的计数排序.我会更新答案。 @rogerdpack - 我更新了问题。似乎在某些时候(在“详细信息”部分,我已经明白只有合并排序用于原语,而不是真正的 timsort,但算法实际上有些相似:两者都是“运行感知合并排序”。无论如何,现在介绍应该更准确。【参考方案3】:

是的,Java 7 将为 Arrays.sort 使用 Timsort。这是提交: http://hg.openjdk.java.net/jdk7/jdk7/jdk/rev/bfd7abda8f79

【讨论】:

以上是关于Java 7 是不是对方法 Arrays.Sort 使用 Tim Sort?的主要内容,如果未能解决你的问题,请参考以下文章

Java1.7之后Arrays.sort对数组排序DualPivotQuicksort.sort

java: 带有 lambda 表达式的 Arrays.sort()

Java中对数组升序排列用Arrays.sort( )方法,那降序排列用啥方法?

java中如何使用arrays.sort()对二维数组排序?

java中如何指定sort的排序方法

Arrays.sort的范例