Java的比较器 Comparable 和 Comparator

在 Java 中,ComparableComparator 是用于对象排序的重要接口。它们提供了不同的排序方式,适用于不同的需求,同时在 Java 底层排序算法中发挥着关键作用。本文将从基础概念、使用方法、排序实现(包括升序、降序)、底层实现原理以及适用场景等方面进行详细解析。


一、 ComparableComparator 的基本概念

在 Java 中,排序通常用于 数组集合(List),两者的排序分别由 Arrays.sort()Collections.sort() 进行,而这两个方法都依赖于 ComparableComparator

1.1 Comparable 接口(自然排序)

  • Comparable 是一个 内部比较器,表示对象本身支持排序规则。

  • 需要在类中实现 compareTo() 方法,定义默认的排序方式。

  • 适用于对象有唯一的自然排序方式,如 IntegerStringDouble 等。

代码示例(按照 age 升序排序):

class Person implements Comparable<Person> {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Person other) {return Integer.compare(this.age, other.age); // 按年龄升序}@Overridepublic 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> {@Overridepublic 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)); // 按姓名排序


3. 底层排序实现

在 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 年提出的改进版 快速排序,其核心思想是:

  1. 选取两个基准点(pivot),将数组划分为 三个部分

    • 小于第一个 pivot 的部分

    • 介于两个 pivot 之间的部分

    • 大于第二个 pivot 的部分

  2. 递归对三个子数组进行排序。

这种优化相比于传统的单轴快速排序,减少了递归调用的次数,提高了排序效率。

源码分析

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),并做了一些优化:

  1. 数据预处理:TimSort 先寻找 已经排序的子序列(run),如果数据本身有部分有序,它可以减少比较次数。

  2. 小规模数据使用插入排序:避免小规模数据使用归并排序导致开销大。

  3. 智能归并:选择合适的子序列进行合并,避免不必要的合并操作,提高效率。

源码分析:
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 的优点:

  • 适用于部分有序的数据,比传统归并排序更快。

  • 避免不必要的合并,提高效率。


2. Collections.sort() 的底层实现

Collections.sort() 主要用于 List 进行排序,它本质上是 ListArrays.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]);
}

它的执行过程

  1. 将 List 转换成数组

  2. 调用 Arrays.sort() 进行排序

  3. 再把排好序的数组元素赋值回 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

四、结论与总结

  1. Comparable 适用于对象有固定的排序方式,如 StringInteger,实现 compareTo() 进行排序。

  2. Comparator 适用于需要多个排序规则的情况,可以使用 compare() 进行定制排序。

  3. 底层排序算法

    • 基本类型使用 Dual-Pivot QuickSort(双轴快排)

    • 对象类型和 List 使用 TimSort(归并排序 + 插入排序优化)

  4. Comparator 更灵活,可以动态传递不同的比较器,适用于多种排序需求。

掌握 ComparableComparator,可以帮助你在开发中实现更高效的排序逻辑!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/74179.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Qlearning强化学习的太赫兹信道信号检测与识别matlab仿真

目录 1.算法仿真效果 2.算法涉及理论知识概要 2.1 太赫兹信道特性 2.2 Q-learning强化学习基础 2.3 基于Q-learning 的太赫兹信道信号检测与识别系统 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2024b仿真结果如下&#xff08;完整代码运行后无水印…

力扣刷题————199.二叉树的右视图

给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5,null,4] 输出&#xff1a;[1,3,4] 解题思路&#xff1a;我们可以想到这…

文件包含漏洞的小点总结

文件本地与远程包含&#xff1a; 文件包含有本地包含与远程包含的区别&#xff1a;本地包含只能包含服务器已经有的问题&#xff1b; 远程包含可以包含一切网络上的文件。 本地包含&#xff1a; ①无限制 感受一下使用phpstudy的文件上传&#xff0c;开启phpstudy的apache…

深度学习处理时间序列(5)

Keras中的循环层 上面的NumPy简单实现对应一个实际的Keras层—SimpleRNN层。不过&#xff0c;二者有一点小区别&#xff1a;SimpleRNN层能够像其他Keras层一样处理序列批量&#xff0c;而不是像NumPy示例中的那样只能处理单个序列。也就是说&#xff0c;它接收形状为(batch_si…

操作系统相关知识点

操作系统在进行线程切换时需要进行哪些动作&#xff1f; 保存当前线程的上下文 保存寄存器状态、保存栈信息。 调度器选择下一个线程 调度算法决策&#xff1a;根据策略&#xff08;如轮转、优先级、公平共享&#xff09;从就绪队列选择目标线程。 处理优先级&#xff1a;实时…

从0到1:Rust 如何用 FFmpeg 和 OpenGL 打造硬核视频特效

引言&#xff1a;视频特效开发的痛点&#xff0c;你中了几个&#xff1f; 视频特效如今无处不在&#xff1a;短视频平台的滤镜美化、直播间的实时美颜、影视后期的电影级调色&#xff0c;甚至 AI 生成内容的动态效果。无论是个人开发者还是团队&#xff0c;视频特效都成了吸引…

【并发编程 | 第一篇】线程相关基础知识

1.并发和并行有什么区别 并发是指多核CPU上的多任务处理&#xff0c;多个任务在同一时刻真正同时执行。 并行是指单核CPU上的多任务处理&#xff0c;多个任务在同一时间段内交替执行&#xff0c;通过时间片轮转实现交替执行&#xff0c;用于解决IO密集型瓶颈。 如何理解线程安…

Kafka 偏移量

在 Apache Kafka 中&#xff0c;偏移量&#xff08;Offset&#xff09;是一个非常重要的概念。它不仅用于标识消息的位置&#xff0c;还在多种场景中发挥关键作用。本文将详细介绍 Kafka 偏移量的核心概念及其使用场景。 一、偏移量的核心概念 1. 定义 偏移量是一个非负整数…

18.redis基本操作

Redis(Remote Dictionary Server)是一个开源的、高性能的键值对(Key-Value)存储数据库,广泛应用于缓存、消息队列、实时分析等场景。它以其极高的读写速度、丰富的数据结构和灵活的应用方式而受到开发者的青睐。 Redis 的主要特点 ​高性能: ​内存存储:Redis 将所有数…

历年跨链合约恶意交易详解(一)——THORChain退款逻辑漏洞

漏洞合约函数 function returnVaultAssets(address router, address payable asgard, Coin[] memory coins, string memory memo) public payable {if (router address(this)){for(uint i 0; i < coins.length; i){_adjustAllowances(asgard, coins[i].asset, coins[i].a…

通俗易懂的讲解SpringBean生命周期

&#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》&#xff08;基础篇&#xff09;、&#xff08;进阶篇&#xff09;、&#xff08;架构篇&#xff09;清华大学出版社签约作家、Java领域优质创作者、CSDN博客专家、…

深入理解 `git pull --rebase` 与 `--allow-unrelated-histories`:区别、原理与实战指南

&#x1f680; git pull --rebase vs --allow-unrelated-histories 全面解析 在日常使用 Git 时&#xff0c;我们经常遇到两种拉取远程代码的方式&#xff1a;git pull --rebase 和 git pull --allow-unrelated-histories。它们的区别是什么&#xff1f;各自适用哪些场景&…

Matlab_Simulink中导入CSV数据与仿真实现方法

前言 在Simulink仿真中&#xff0c;常需将外部数据&#xff08;如CSV文件或MATLAB工作空间变量&#xff09;作为输入信号驱动模型。本文介绍如何高效导入CSV数据至MATLAB工作空间&#xff0c;并通过From Workspace模块实现数据到Simulink的精确传输&#xff0c;适用于运动控制…

Spring Boot 中 JdbcTemplate 处理枚举类型转换 和 减少数据库连接的方法 的详细说明,包含代码示例和关键要点

以下是 Spring Boot 中 JdbcTemplate 处理枚举类型转换 和 减少数据库连接的方法 的详细说明&#xff0c;包含代码示例和关键要点&#xff1a; 一、JdbcTemplate 处理枚举类型转换 1. 场景说明 假设数据库存储的是枚举的 String 或 int 值&#xff0c;但 Java 实体类使用 enu…

API 安全之认证鉴权

作者&#xff1a;半天 前言 API 作为企业的重要数字资源&#xff0c;在给企业带来巨大便利的同时也带来了新的安全问题&#xff0c;一旦被攻击可能导致数据泄漏重大安全问题&#xff0c;从而给企业的业务发展带来极大的安全风险。正是在这样的背景下&#xff0c;OpenAPI 规范…

MATLAB绘图配色包说明

本栏目将分享MATLAB数据分析图表&#xff0c;该贴讲述配色包的使用 将配色包colormap_nclCM文件夹添加到路径close all&#xff08;尽量不要删&#xff09;&#xff0c;使用map colormap(nclCM(309))时会多出来一张空白图片。配色资源来自slandarer&#xff1b;找不到合适颜色…

Oracle 数据库系统全面详解

Oracle 数据库是全球领先的关系型数据库管理系统(RDBMS)&#xff0c;由 Oracle 公司开发。它为企业级应用提供了高性能、高可用性、安全性和可扩展性的数据管理解决方案。 目录 一、Oracle 数据库体系结构 1. 物理存储结构 主要组件&#xff1a; 存储层次&#xff1a; 2. …

Flink介绍——发展历史

引入 我们整个大数据处理里面的计算模式主要可以分为以下四种&#xff1a; 批量计算&#xff08;batch computing&#xff09; MapReduce Hive Spark Flink pig流式计算&#xff08;stream computing&#xff09; Storm SparkStreaming/StructuredStreaming Flink Samza交互计…

在MFC中使用Qt(四):使用属性表(Property Sheet)实现自动化Qt编译流程

前言 首先回顾下前面文章介绍的&#xff1a; 在MFC中使用Qt&#xff08;一&#xff09;&#xff1a;玩腻了MFC&#xff0c;试试在MFC中使用Qt&#xff01;&#xff08;手动配置编译Qt&#xff09; 在MFC中使用Qt&#xff08;二&#xff09;&#xff1a;实现Qt文件的自动编译流…

Go红队开发— 收官工具

文章目录 免责声明个人武器开发美观输出Whois查询反查ip目录扫描子域名爆破被动扫描主动扫描(字典爆破)CDN检测 免责声明 &#x1f4a1; 本博客绝不涉及任何非法用途。 &#x1f4a1; 使用者风险自担&#xff0c;违规后果自负。 &#x1f4a1; 守法为先&#xff0c;技术向善。 …