美文网首页Java
[Java]Java中的排序原来可以这么玩

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

作者: AbstractCulture | 来源:发表于2021-02-22 01:11 被阅读0次

    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<Student> 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.<br>
     * 在一次使用lambda的时候对Java排序产生了兴趣。因此本demo用来研究集合排序.<br>
     * 2021/2/18 20:16
     */
    @SuppressWarnings("unchecked")
    public class CollectionSort {
        /**
         * 数字列表
         */
        protected final static List<Integer> numberList = Lists.newArrayList(2, 3, 4, 1, 5, 2, 5, 6, 7, 99);
        /**
         * 存储不同年龄段的学生列表
         */
        protected static List<Student> 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对集合进行升序排序.<br>
         * 要求集合中的对象实现Comparable接口.<br>
         */
        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方法

        /**
         * 这里我们使用了集合存储学生的信息.<br>
         * 如果此时希望按照年龄来进行排序,可以在Student类中实现Comparable接口。<br>
         * 然后再使用Collections.sort进行排序.<br>
         */
        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来进行排序.<br>
         * 比较器接收两个参数,可以理解成,给定两个学生对象,你实现compare方法来规定按什么顺序进行排序.<br>
         * 比较规则:the value 0 if x == y; a value less than 0 if x < y; and a value greater than 0 if x > y.<br>
         * 首先我们通过匿名内部类来完成这项工作.<br>
         *
         * @see Comparator
         */
        public static void sortByCollectionsComparator() {
            createStudentList();
            Collections.sort(studentList, new Comparator<Student>() {
                @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来简化匿名内部类.<br>
         */
        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流,让你的集合操作更多元化.<br>
         */
        public static void sortByStream() {
            createStudentList();
            // 升序
            List<Student> 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<Student> 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值保护比较器来避免空指针异常<br>
         */
        public static void sortByStreamWithNullFirst() {
            createStudentList();
            studentList.get(0).setAge(null);
            // 升序
            List<Student> 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值保护比较器来避免空指针异常<br>
         */
        public static void sortByStreamWithNullFirst() {
            createStudentList();
            studentList.get(0).setAge(null);
            // 升序,空值排在第一位
            List<Student> 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<Student> 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<Student> 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中的排序原来可以这么玩

        本文链接:https://www.haomeiwen.com/subject/bkzcfltx.html