前言
在 java 中,comparable
和 comparator
是用于对象排序的重要接口。它们提供了不同的排序方式,适用于不同的需求,同时在 java 底层排序算法中发挥着关键作用。本文将从基础概念、使用方法、排序实现(包括升序、降序)、底层实现原理以及适用场景等方面进行详细解析。
一、 comparable 和 comparator 的基本概念
在 java 中,排序通常用于 数组 和 集合(list),两者的排序分别由 arrays.sort()
和 collections.sort()
进行,而这两个方法都依赖于 comparable
和 comparator
。
1.1 comparable 接口(自然排序)
comparable
是一个 内部比较器,表示对象本身支持排序规则。需要在类中实现
compareto()
方法,定义默认的排序方式。适用于对象有唯一的自然排序方式,如
integer
、string
、double
等。
代码示例(按照 age 升序排序):
class person implements comparable<person> { private string name; private int age; public person(string name, int age) { this.name = name; this.age = age; } @override public int compareto(person other) { return integer.compare(this.age, other.age); // 按年龄升序 } @override public string tostring() { return name + " (" + age + ")"; } } public class comparableexample { public static void main(string[] args) { person[] people = { new person("alice", 25), new person("bob", 22), new person("charlie", 30) }; arrays.sort(people); // 按 `comparable` 规则排序 system.out.println(arrays.tostring(people)); } }
输出结果:
[bob (22), alice (25), charlie (30)]
comparable
的排序方式是 类内部固定的,所有调用 sort()
的地方都使用同样的规则。
1.2 comparator 接口(自定义排序)
comparator
是一个 外部比较器,可以用于自定义排序规则。需要实现
compare()
方法,可以在不同场景使用不同的比较逻辑。适用于对象有 多种排序需求,如按年龄、姓名、id 等。
代码示例(按 name 进行字母升序排序):
class namecomparator implements comparator<person> { @override public int compare(person p1, person p2) { return p1.name.compareto(p2.name); // 按名称字母升序 } } public class comparatorexample { public static void main(string[] args) { list<person> people = new arraylist<>(); people.add(new person("alice", 25)); people.add(new person("bob", 22)); people.add(new person("charlie", 30)); people.sort(new namecomparator()); // 使用外部比较器进行排序 system.out.println(people); } }
输出结果:
[alice (25), bob (22), charlie (30)]
使用 comparator
可以定义多种排序规则,不同的需求可以使用不同的比较器,非常灵活。
二、升序和降序排序实现
2.1 comparable 的升序和降序
在 comparable
中,只能通过修改 compareto()
方法来改变排序顺序:
@override public int compareto(person other) { return integer.compare(other.age, this.age); // 降序排序 }
2.2 comparator 的升序和降序
使用 comparator
可以轻松实现 不同排序方式:
comparator<person> ageascending = comparator.comparingint(p -> p.age); // 按年龄升序 comparator<person> agedescending = (p1, p2) -> integer.compare(p2.age, p1.age); // 按年龄降序
代码示例:
people.sort(ageascending); // 升序排序
people.sort(agedescending); // 降序排序
使用 java 8 的 lambda 表达式:
people.sort((p1, p2) -> p1.name.compareto(p2.name)); // 按姓名排序
三. 底层排序实现
在 java 中,arrays.sort()
和 collections.sort()
在不同数据类型下采用不同的排序算法:
3.1 arrays.sort()(适用于数组)
arrays.sort()
主要用于 数组排序,其底层实现因数据类型不同而有所不同:基本类型(
int[]
、double[]
等):使用 dual-pivot quicksort(双轴快速排序),这是quicksort
的一种优化版本。对象类型(
integer[]
、string[]
等):使用 timsort(归并排序 + 插入排序的优化组合)。
3.1.1 基本类型:双轴快速排序
对于 int[]
、double[]
等基本数据类型的数组排序,arrays.sort()
使用的是 双轴快速排序(dual-pivot quicksort),它是由 vladimir yaroslavskiy 在 2009 年提出的改进版 快速排序,其核心思想是:
选取两个基准点(pivot),将数组划分为 三个部分:
小于第一个 pivot 的部分
介于两个 pivot 之间的部分
大于第二个 pivot 的部分
递归对三个子数组进行排序。
这种优化相比于传统的单轴快速排序,减少了递归调用的次数,提高了排序效率。
源码分析
在 arrays.sort(int[] a) 的源码中:
public static void sort(int[] a) { dualpivotquicksort.sort(a, 0, a.length - 1, null, 0, 0); }
它会调用 dualpivotquicksort.sort()
,具体实现如下:
static void sort(int[] a, int left, int right, int[] work, int workbase, int worklen) { if (right - left < quicksort_threshold) { insertionsort(a, left, right); // 小数组使用插入排序 return; } int pivot1 = a[left], pivot2 = a[right]; if (pivot1 > pivot2) { swap(a, left, right); pivot1 = a[left]; pivot2 = a[right]; } int less = left + 1; int great = right - 1; for (int k = less; k <= great; k++) { if (a[k] < pivot1) { swap(a, k, less++); } else if (a[k] > pivot2) { swap(a, k, great--); } } sort(a, left, less - 1, work, workbase, worklen); sort(a, less, great, work, workbase, worklen); sort(a, great + 1, right, work, workbase, worklen); }
可以看出,dual-pivot quicksort 主要优化点:
双轴划分:比传统快速排序减少递归层数,提高效率。
小数据量时使用插入排序,减少不必要的递归。
3.1.2对象类型:timsort(改进版归并排序)
对于对象数组(如 integer[]
、string[]
),java 采用的是 timsort,它结合了 归并排序(mergesort)+ 插入排序(insertionsort),并做了一些优化:
数据预处理:timsort 先寻找 已经排序的子序列(run),如果数据本身有部分有序,它可以减少比较次数。
小规模数据使用插入排序:避免小规模数据使用归并排序导致开销大。
智能归并:选择合适的子序列进行合并,避免不必要的合并操作,提高效率。
源码分析:
public static <t> void sort(t[] a, comparator<? super t> c) { if (c == null) { arrays.sort(a); // 调用默认的 comparable 方式排序 } else { timsort.sort(a, c); // 使用 comparator 进行排序 } }
核心代码:
static <t> void sort(t[] a, comparator<? super t> c) { int lo = 0, hi = a.length; if (hi - lo < insertion_sort_threshold) { insertionsort(a, lo, hi, c); // 小数据量使用插入排序 return; } int mid = (lo + hi) >>> 1; sort(a, lo, mid, c); sort(a, mid, hi, c); merge(a, lo, mid, hi, c); // 归并两个有序数组 }
timsort 的优点:
适用于部分有序的数据,比传统归并排序更快。
避免不必要的合并,提高效率。
3.2. collections.sort() 的底层实现
collections.sort()
主要用于 list 进行排序,它本质上是 list
的 arrays.sort()
,所以它的底层也是 timsort。
public static <t extends comparable<? super t>> void sort(list<t> list) { object[] array = list.toarray(); arrays.sort(array); for (int i = 0; i < list.size(); i++) list.set(i, (t) array[i]); }
它的执行过程:
将 list 转换成数组
调用 arrays.sort() 进行排序
再把排好序的数组元素赋值回 list
这意味着 collections.sort()
的底层仍然是 timsort。
排序方法 | 适用范围 | 底层实现 |
---|---|---|
arrays.sort(int[]) | 基本类型数组 | dual-pivot quicksort(双轴快速排序) |
arrays.sort(t[]) | 对象数组 | timsort(归并排序 + 插入排序优化) |
collections.sort(list<t>) | list 容器 | timsort(底层调用 arrays.sort()) |
arrays.sort(arr, comparator) | 自定义对象排序 | timsort(支持 comparator) |
四、结论与总结
comparable 适用于对象有固定的排序方式,如
string
、integer
,实现compareto()
进行排序。comparator 适用于需要多个排序规则的情况,可以使用
compare()
进行定制排序。底层排序算法:
基本类型使用 dual-pivot quicksort(双轴快排)。
对象类型和 list 使用 timsort(归并排序 + 插入排序优化)。
comparator 更灵活,可以动态传递不同的比较器,适用于多种排序需求。
掌握 comparable
和 comparator
,可以帮助你在开发中实现更高效的排序逻辑!
到此这篇关于java中比较器comparable和comparator的文章就介绍到这了,更多相关java比较器comparable和comparator内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论