百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

「Java必修课」一图说尽排序,一文细说Sorting

toyiye 2024-05-25 20:11 17 浏览 0 评论

简说排序

排序是极其常见的使用场景,因为在生活中就有很多这样的实例。国家GDP排名、奥运奖牌排名、明星粉丝排名等,各大排行榜,给人的既是动力,也是压力。

而讲到排序,就会有各种排序算法和相关实现,本文不讲任何排序算法,而只专注于讲使用。通过实例给大家展示,我们可以了解怎样使用既有的工具进行排序。Linux之父说:

Talk is cheap. show me the code!

本文JDK版本为Java 8,但并不代表所介绍到的所有方法只能在JDK1.8上跑,部分方法在之前的版本就已经给出。

如下本次整理的图,记住图中的方法,就能轻松应对大多数场景,赶紧收藏起来吧,哈哈

---

两个接口

Comparable

先上代码:

package java.lang;
public interface Comparable<T> { 
 public int compareTo(T o);
}

可以看出这个接口只有一个方法,这个方法只有一个参数,实现了这个接口的类就可以和同类进行比较了。这个方法所实现的,就是比较法则,也是说,它表示如何对两个对象进行比较。它返回的是一个整数int:

  • 返回正数,代表当前对象大于被比较的对象;
  • 返回0,代表当前对象等于于被比较的对象;
  • 返回负数,代表当前对象小于被比较的对象。

实现了该接口后,我们就可以使用Arrays.sort()和Collections.sort()来进行排序了。不然对象没有比较法则,程序肯定是不知道如何进行比较排序的。像我们常用的类String、Integer、Double、Date等,JDK都帮我们实现了Comparable接口,我们可以直接对这类对象进行比较排序。举个例子,Date Comparable的实现:

public int compareTo(Date anotherDate) {
 long thisTime = getMillisOf(this);
 long anotherTime = getMillisOf(anotherDate);
 return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1));
}

需要注意的是,当我们自己去实现Comparable接口时,一定要注意与equals()方法保持一致。当两个对象是equals的,compare的结果应该是相等的。

Comparator

还是先看代码,看看接口定义吧:

package java.util;
@FunctionalInterface
public interface Comparator<T> { 
 int compare(T o1, T o2); 
}

它是一个函数式接口,它的compare方法有两个参数,代表进行比较的两个对象。这个接口代表了可以作为某种对象比较的一种法则,或叫一种策略。它的返回值正负代表意义与Comparable接口的方法一样。它的使用通常会有三种方式:

  1. 实现类
  2. 匿名类
  3. Lambda表达式

在Java8之后,我们一般用Lambda比较多,也比较灵活优雅。

两个接口的比较

两个接口功能都是用于比较排序,但其实有很大的区别。

  1. 两者方法参数不同,Comparable只有一个参数,表示被比较的对象,因为它的方法是位于需要比较的类里的,所以只要一个参数就可以了;而Comparator的比较方法则有两个参数,分别表示比较对象和被比较对象。
  2. Comparable与对象绑定,位于对象内,我们可以称之为内比较器;而Comparator是独立于需要比较的类的,我们可以称为外比较器
  3. 当类实现了Comparable方法后,比较法则就确定了,我们称之为自然比较方法,我们无法给它实现多种比较方法;而因为Comparator独立于外,我们可以为同一个类提供多种Comparator的实现,这样来提供多种比较方法/策略,如升序倒序,因此我们也可以将Comparator看成是一种策略模式

相对于Comparable,Comparator有一定的灵活性,假如一个类并没有实现Comparable接口,并且这个类是无法修改的,我们就要通过提供Comparator来进行比较排序。Comparator这种模式实现了数据与算法的解耦合,对于维护也是很方便的。

工具类

十分友好的是,JDK为我们提供了工具类,它们的静态方法可以帮助我们直接对数组和List进行排序。

数组排序Arrays

Arrays的sort方法可以对已经实现了Comparable接口的进行排序,同时还可指定排序的范围。

//Arrays.sort对String进行排序
String[] strings = {"de", "dc", "aA", "As", "k", "b"};
Arrays.sort(strings);
assertTrue(Arrays.equals(strings,
 new String[]{"As", "aA", "b", "dc", "de", "k"}));

指定范围排序,需要注意的是,index是从0开始算的,包含fromIndex,不包含toIndex:

//Arrays.sort指定范围排序
strings = new String[]{"z", "a", "d", "b"};
Arrays.sort(strings, 0, 3);
assertTrue(Arrays.equals(strings,
 new String[]{"a", "d", "z", "b"}));

对于基本类型,一样可以进行排序,并不需要使用封装类:

//Arrays.sort对基本类型排序
int[] nums = {3, 1, 20, 2, 38, 2, 94};
Arrays.sort(nums);
assertTrue(Arrays.equals(nums,
 new int[]{1, 2, 2, 3, 20, 38, 94}));

还能多线程进行排序,其实是拆分成多个子数组再进行排序,最终再合并结果。

//Arrays.parallelSort多线程排序
nums = new int[]{3, 1, 20, 2, 38, 2, 94};
Arrays.parallelSort(nums);
assertTrue(Arrays.equals(nums,
 new int[]{1, 2, 2, 3, 20, 38, 94}));

对于没有实现Comparable的类也可以使用,但需要提供Comparator来指定比较策略。本文的没有实现Comparable接口的类Person如下:

import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Person {
 private String name;
 private int age;
}

排序:

//Arrays.sort提供Comparator进行排序
Person[] persons = new Person[]{
 new Person("Larry", 18),
 new Person("David", 30),
 new Person("James", 20),
 new Person("Harry", 18)};
Arrays.sort(persons, Comparator.comparingInt(Person::getAge));
assertTrue(Arrays.equals(persons, new Person[]{
 new Person("Larry", 18),
 new Person("Harry", 18),
 new Person("James", 20),
 new Person("David", 30)}));

List排序Collections

JDK的Collections工具类提供了排序方法,可以方便使用。对于实现Comparable的类进行排序:

//Collections.sort对于实现Comparable的类进行排序
List<String> names = asList("Larry", "Harry", "James", "David");
Collections.sort(names);
assertEquals(names, asList("David", "Harry", "James", "Larry"));

提供Comparator进行排序:

//Collections.sort提供Comparator进行排序
List<Person> persons2 = asList(
 new Person("Larry", 18),
 new Person("David", 30),
 new Person("James", 20),
 new Person("Harry", 18));
Collections.sort(persons2, Comparator.comparingInt(Person::getAge));
assertEquals(persons2, asList(
 new Person("Larry", 18),
 new Person("Harry", 18),
 new Person("James", 20),
 new Person("David", 30)));

反序:只是把List反过来,并不代表一定是按照大小顺序的:

//Collections.reverse反序
names = asList("Larry", "Harry", "James", "David");
Collections.reverse(names);
assertEquals(names, asList("David", "James", "Harry", "Larry"));

成员方法

List排序

List接口有sort(Comparator c)方法,可以实现对自身的排序,会影响自身的顺序

//List.sort排序
names = asList("Larry", "Harry", "James", "David");
names.sort(Comparator.naturalOrder());
assertEquals(names, asList("David", "Harry", "James", "Larry"));

Stream排序

Stream提供了sorted()和sorted(Comparator comparator)进行排序,会返回一个新的Stream。

//Stream.sorted排序
names = asList("Larry", "Harry", "James", "David");
List<String> result = names.stream()
 .sorted()
 .collect(Collectors.toList());
assertEquals(result, asList("David", "Harry", "James", "Larry"));
//Stream.sorted提供Comparator排序
names = asList("Larry", "Harry", "James", "David");
result = names.stream()
 .sorted(Comparator.naturalOrder())
 .collect(Collectors.toList());
assertEquals(result, asList("David", "Harry", "James", "Larry"));

方便对象排序的Comparator

单字段排序

对类的单字段进行排序很简单,只要提供形如:

  • Comparator.comparing(类名::属性getter)

的Comparator就行了。如果需要倒序,就需要:

  • Comparator.comparing(类名::属性getter).reversed()
  • Comparator.comparing(类名::属性getter, Comparator.reverseOrder())

具体代码使用(为了不破坏List的原有顺序,我们都使用Stream来操作):

//单字段排序-升序
List<Person> personList = asList(
 new Person("Larry", 18),
 new Person("David", 30),
 new Person("David", 3),
 new Person("James", 20),
 new Person("Harry", 18));
List<Person> personResult = personList.stream()
 .sorted(Comparator.comparing(Person::getName))
 .collect(Collectors.toList());
assertEquals(personResult, asList(
 new Person("David", 30),
 new Person("David", 3),
 new Person("Harry", 18),
 new Person("James", 20),
 new Person("Larry", 18)));
//单字段排序-倒序1
personResult = personList.stream()
 .sorted(Comparator.comparing(Person::getName).reversed())
 .collect(Collectors.toList());
assertEquals(personResult, asList(
 new Person("Larry", 18),
 new Person("James", 20),
 new Person("Harry", 18),
 new Person("David", 30),
 new Person("David", 3)));
//单字段排序-倒序2
personResult = personList.stream()
 .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder()))
 .collect(Collectors.toList());
assertEquals(personResult, asList(
 new Person("Larry", 18),
 new Person("James", 20),
 new Person("Harry", 18),
 new Person("David", 30),
 new Person("David", 3)));

多字段排序

多字段其实也很方便,只需要用thenComparing进行连接就可以:Comparator.comparing(类名::属性一getter).thenComparing(类名::属性二getter)具体代码使用例子如下:

//多字段排序-1升2升
personResult = personList.stream()
 .sorted(Comparator.comparing(Person::getName)
 .thenComparing(Person::getAge))
 .collect(Collectors.toList());
assertEquals(personResult, asList(
 new Person("David", 3),
 new Person("David", 30),
 new Person("Harry", 18),
 new Person("James", 20),
 new Person("Larry", 18)));
//多字段排序-1升2倒
personResult = personList.stream()
 .sorted(Comparator.comparing(Person::getName)
 .thenComparing(Person::getAge, Comparator.reverseOrder()))
 .collect(Collectors.toList());
assertEquals(personResult, asList(
 new Person("David", 30),
 new Person("David", 3),
 new Person("Harry", 18),
 new Person("James", 20),
 new Person("Larry", 18)));
//多字段排序-1倒2升
personResult = personList.stream()
 .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder())
 .thenComparing(Person::getAge))
 .collect(Collectors.toList());
assertEquals(personResult, asList(
 new Person("Larry", 18),
 new Person("James", 20),
 new Person("Harry", 18),
 new Person("David", 3),
 new Person("David", 30)));
//多字段排序-1倒2倒
personResult = personList.stream()
 .sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder())
 .thenComparing(Person::getAge, Comparator.reverseOrder()))
 .collect(Collectors.toList());
assertEquals(personResult, asList(
 new Person("Larry", 18),
 new Person("James", 20),
 new Person("Harry", 18),
 new Person("David", 30),
 new Person("David", 3)));

总结

本文从比较排序相关的两个接口(Comparable和Comparator)讲起,并以代码实例的形式,讲解了Array、List、Stream排序的方法,这应该可以覆盖大部分Java排序的使用场景。对于其它集合类如Set和Map,一样可以进行排序处理,可以将它们转化为Stream然后再进行排序。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码