[Java]Java中的排序原来可以这么玩

1. Java中的排序算法

讲Java中的集合排序前,我们来一起思考一下: 什么样的排序算法适合做为标准类库的算法?

  • 对比各种常见排序算法的优劣性
算法 稳定性 时间复杂度 空间复杂度
选择排序 不稳定 1
希尔排序 不稳定 说法很多,N^(1.25-2) 1
堆排序 不稳定 NlogN 1
快速排序 不稳定 NlogN lgN
插入排序 稳定 N-N² 1
归并排序 稳定 NlogN N

其中,快速排序算法对于多数的情况都是效率较高的,其内循环中的指令很少并且总是顺序地访问数据,可以利用缓存(预加载),但是稳定性无法保证(如果待排序的元素序列本身即为有序的状态).
如果对稳定性有要求,那么归并排序可能是更为适合的,它的弊端是需要更多的空间(通常编写归并排序,我们需要创建临时数组).
关于局部性原理知识,可以通往下面链接看知乎大神如何讨论:
如何理解计算机操作系统中的局部性原理?
关于快速排序算法的讨论:
快速排序到底有多快
排序算法(三): Quick Sort 快速排序(包含三向切分)

  • 原始类型数据与引用类型数据

这里说一下笔者的一些简单理解
原始类型的数据,大小比较都是可以确定的,比如:
比较int大小,可以直接用 a > b这种方式进行比较.
但是Java中还提供了Comparable接口,它通过实现该接口的方法-compareTo来决定排列顺序.
这时,无法通过直接比较数值的大小,而是通过引用来执行方法得到结果,需要额外的空间.

为此,Java中对 原始类型数组(类型为 int、long、short、char、byte、boolean、float 或 double 的数组) 采用优化的快速排序算法-DualPivotQuicksort.
对于引用类型数据的集合采用基于归并排序的TimSort算法.

2. 使用集合排序需要注意的问题

禁止对不可变列表(unmodifiableList)进行排序,传入的列表可以为必须是可修改的, 但不必是可以改变大小的.

  • 错误示范
Student xiaoMing = Student.builder().age(20).name("20岁的小明").build();
Student xiaoHong = Student.builder().age(22).name("22岁的小红").build();
Student xiaoHua = Student.builder().age(25).name("25岁的小华").build();
List studentList = Collections.unmodifiableList(Lists.newArrayList(xiaoHong, xiaoMing, xiaoHua));
// 使用不可变集合,会抛出UnsupportedOperationException异常
Collections.sort(studentList);
studentList.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n"));

3. 实战Java排序

3.1 数据源准备

为了更好地复用数据,我们先声明两个数据列表:
一个为数值型的数据列表
一个为业务对象的数据列表

  • 业务对象-Student
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * @author jaymin
 */
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student implements Serializable, Comparable {
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 名字
     */
    private String name;

    /**
     * the value 0 if x == y; a value less than 0 if x < y; and a value greater than 0 if x > y
     * @param o 与之比较的其他学生
     * @return
     */
    @Override
    public int compareTo(Object o) {
        Student otherStudent = (Student) o;
        return Integer.compare(this.age, otherStudent.age);
    }
}
  • 声明成员变量
import com.google.common.collect.Lists;
import com.tea.modules.model.Student;
import com.tea.modules.util.JacksonUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author jaymin.
* 在一次使用lambda的时候对Java排序产生了兴趣。因此本demo用来研究集合排序.
* 2021/2/18 20:16 */ @SuppressWarnings("unchecked") public class CollectionSort { /** * 数字列表 */ protected final static List numberList = Lists.newArrayList(2, 3, 4, 1, 5, 2, 5, 6, 7, 99); /** * 存储不同年龄段的学生列表 */ protected static List studentList; /** * 构建出学生列表参数 */ private static void createStudentList() { Student xiaoMing = Student.builder().age(20).name("20岁的小明").build(); Student xiaoHong = Student.builder().age(22).name("22岁的小红").build(); Student xiaoHua = Student.builder().age(25).name("25岁的小华").build(); studentList = Lists.newArrayList(xiaoHong, xiaoMing, xiaoHua); } }

3.2 对原始类型列表进行排序

使用Collections.sort可以直接对Integer列表进行排序

    /**
     * 使用Collections.sort对集合进行升序排序.
* 要求集合中的对象实现Comparable接口.
*/ public static void sortByCollections() { Collections.sort(numberList); numberList.forEach(e -> System.out.print(e + "-")); }
  • Result
1-2-2-3-4-5-5-6-7-99-

3.3 对实现了Comparable接口的对象进行排序

Collections.sort中的对象需要重写compareTo方法

    /**
     * 这里我们使用了集合存储学生的信息.
* 如果此时希望按照年龄来进行排序,可以在Student类中实现Comparable接口。
* 然后再使用Collections.sort进行排序.
*/ public static void sortByCollectionsComparable() { createStudentList(); Collections.sort(studentList); studentList.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); }
  • Result
{
  "age" : 20,
  "name" : "20岁的小明"
}
{
  "age" : 22,
  "name" : "22岁的小红"
}
{
  "age" : 25,
  "name" : "25岁的小华"
}

3.4 使用比较器-Comparator来进行排序

通过匿名内部类来创建Comparator

    /**
     * 不实现接口,使用比较器-Comparator来进行排序.
* 比较器接收两个参数,可以理解成,给定两个学生对象,你实现compare方法来规定按什么顺序进行排序.
* 比较规则:the value 0 if x == y; a value less than 0 if x < y; and a value greater than 0 if x > y.
* 首先我们通过匿名内部类来完成这项工作.
* * @see Comparator */ public static void sortByCollectionsComparator() { createStudentList(); Collections.sort(studentList, new Comparator() { @Override public int compare(Student studentA, Student studentB) { return Integer.compare(studentA.getAge(), studentB.getAge()); } }); studentList.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); }

输出结果同上,这里就不贴了

3.5 Java8中使用lambda来简化匿名内部类.

    /**
     * 在Java8中,你可以使用lambda来简化匿名内部类.
*/ public static void sortByCollectionsComparatorInJava8() { createStudentList(); // 常规的lambda Collections.sort(studentList, (studentA, studentB) -> Integer.compare(studentA.getAge(), studentB.getAge())); // 同时,你可以将这种方式改写成方法引用 Collections.sort(studentList, Comparator.comparingInt(Student::getAge)); studentList.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); }

输出结果同上,这里就不贴了

3.6 使用Java8的stream进行排序

    /**
     * 使用Java8的stream流,让你的集合操作更多元化.
*/ public static void sortByStream() { createStudentList(); // 升序 List result = studentList.stream() .sorted(Comparator.comparing(Student::getAge)) .collect(Collectors.toList()); result.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); System.out.println("------------reversedOrder---------------"); // 倒序 List reversedOrderResult = studentList.stream() .sorted(Comparator.comparingInt(Student::getAge).reversed()) .collect(Collectors.toList()); reversedOrderResult.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); }
  • Result
{
  "age" : 20,
  "name" : "20岁的小明"
}
{
  "age" : 22,
  "name" : "22岁的小红"
}
{
  "age" : 25,
  "name" : "25岁的小华"
}
------------reversedOrder---------------
{
  "age" : 25,
  "name" : "25岁的小华"
}
{
  "age" : 22,
  "name" : "22岁的小红"
}
{
  "age" : 20,
  "name" : "20岁的小明"
}
  • 空指针保护的Comparator.comparing

在上面的实验中,我们使用了Comparator.comparing()来进行比较,但是,如果student对象中的age为空时,我们会遇到空指针异常.

  • 触发空指针异常
    /**
     * 使用null值保护比较器来避免空指针异常
*/ public static void sortByStreamWithNullFirst() { createStudentList(); studentList.get(0).setAge(null); // 升序 List result = studentList.stream() .sorted(Comparator.comparing(Student::getAge)) .collect(Collectors.toList()); result.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); }
  • 使用nullFirst和nullLast来避免空指针异常

nullFirst表示遇到null时,将包含null属性的对象排列在第一位.
nullsLast表示遇到null时,将包含null属性的对象排列在最后一位.
两者都需要声明排序顺序:
升序为:Comparator.naturalOrder()
降序为:Comparator.reverseOrder()

    /**
     * 使用null值保护比较器来避免空指针异常
*/ public static void sortByStreamWithNullFirst() { createStudentList(); studentList.get(0).setAge(null); // 升序,空值排在第一位 List result = studentList.stream() .sorted(Comparator.comparing(Student::getAge,Comparator.nullsFirst(Comparator.naturalOrder()))) .collect(Collectors.toList()); result.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); System.out.println("******************** Null First reverseOrderResult*****************************"); // 降序,空值排在第一位 List reverseOrderResult = studentList.stream() .sorted(Comparator.comparing(Student::getAge,Comparator.nullsFirst(Comparator.reverseOrder()))) .collect(Collectors.toList()); reverseOrderResult.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); System.out.println("******************** Null Last reverseOrderResult*****************************"); // 降序,空值排在第一位 List nullLastResult = studentList.stream() .sorted(Comparator.comparing(Student::getAge,Comparator.nullsLast(Comparator.reverseOrder()))) .collect(Collectors.toList()); nullLastResult.forEach(e -> System.out.print(JacksonUtils.toJsonString(e) + "\n")); }
  • Result
{
  "name" : "22岁的小红"
}
{
  "age" : 20,
  "name" : "20岁的小明"
}
{
  "age" : 25,
  "name" : "25岁的小华"
}
******************** Null First reverseOrderResult*****************************
{
  "name" : "22岁的小红"
}
{
  "age" : 25,
  "name" : "25岁的小华"
}
{
  "age" : 20,
  "name" : "20岁的小明"
}
******************** Null Last reverseOrderResult*****************************
{
  "age" : 25,
  "name" : "25岁的小华"
}
{
  "age" : 20,
  "name" : "20岁的小明"
}
{
  "name" : "22岁的小红"
}

源码地址

所有源码已经上传到我的个人仓库,有兴趣的可以pull下来进行demo.
仓库地址
代码引用地址:com.tea.modules.java8.collection.sort.CollectionSort

你可能感兴趣的:([Java]Java中的排序原来可以这么玩)