Java集合或Map中元素排序及过滤

在Java中,对集合或Map中元素进行排序或过滤是一个频繁操作。这里以List为例介绍下如何在集合中实现元素的排序和过滤功能。对于非List元素(Set、Map)等,一方面可以参考List使用类似的方法,另一方面可以将其转换成List并执行相关方法,关于Set或Map等与List的转换,可以参考笔者之前的文章

排序

在日常的开发中,开发人员经常使用数据库的排序能力对待查询的数据进行排序。但是,业务上也会遇到没有数据库的场景,如数据存储在文件中,且数据量不大,这个时候就需要在内存中排序。对于内存排序,在算法领域也将其称之为内部排序。常见的内部排序算法有:快速排序、冒泡排序、归并排序、堆排序等。在日常的开发工作中,不推荐自己实现一个排序算法,而是推荐优先使用类库已提供的排序方法。对于数组,这里推荐使用Arrays.sort方法。对于List中元素排序,这里推荐使用Collections.sort方法或Stream.sorted方法。

使用Arrays.sort方法

JDK提供Arrays.sort方法用于实现数组元素排序。查看该方法源码如下:

// Arrays.java
public static void sort(int[] a) {
    DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
public static void sort(int[] a, int fromIndex, int toIndex) {
    rangeCheck(a.length, fromIndex, toIndex);
    DualPivotQuicksort.sort(a, fromIndex, toIndex - 1, null, 0, 0);
}
public static <T> void sort(T[] a, Comparator<? super T> c) {
    if (c == null) {
        sort(a);
    } else {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, c);
        else
            TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
}
public static <T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? super T> c) {
    if (c == null) {
        sort(a, fromIndex, toIndex);
    } else {
        rangeCheck(a.length, fromIndex, toIndex);
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, fromIndex, toIndex, c);
        else
            TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
    }
}

对于基本数据类型,如int、short、char、byte、float、double等都提供了相似的方法。其次,支持排序的场景主要有以下几种:对所有元素进行排序、对部分元素进行排序、对所有元素排序并指定比较器、对部分元素排序并制定比较器。
进一步阅读sort方法源码可知,对于没有指定比较器的数组来说,sort方法使用Dual-Pivot快速排序算法来实现数组中元素的排序,且大多数情况下,比传统的One-Pivot快速排序更快。无论是Dual-Pivot快速排序算法,还是传统的One-Pivot快速排序,其时间复杂度都是 O ( n l o g ( n ) ) O(n log (n)) O(nlog(n))。更多Dual-Pivot快速排序算法实现,可以参考DualPivotQuicksort源码。对于指定比较器的数组来说,sort方法使用归并排序算或Tim排序算法。
sort方法声明如下,以int为例,其他的数据类型可以参考学习:

sort(int[] a):对指定数组按数字升序排序。
sort(int[] a,int formIndex, int toIndex):对指定数组的指定范围按数字升序排序。
sort(Integer[] a, Comparator<? supre T> c): 根据指定比较器产生的顺序对指定对象数组进行排序。
sort(Integer[] a, int formIndex, int toIndex, Comparator<? supre T> c): 根据指定比较器产生的顺序对指定数组的指定对象数组进行排序。

接下来将给出使用示例:

Arrays.sort(new int[]{7, 6, 5, 4, 3, 2, 1});           // 排序后数组是:1,2,3,4,5,6,7
Arrays.sort(new int[]{3, 2, 1, 4, 5, 6, 7}, 0, 2);     // 排序后数组是:1,2,3,4,5,6,7
// 排序后数组是:7,6,5,4,3,2,1
Arrays.sort(new Integer[]{1, 2, 3, 4, 5, 6, 7}, (a, b) -> {
    if (a > b) {
        return -1;
    } else if (a == b) {
        return 0;
    } else {
        return 1;
    }
});
// 排序后数组是:7,6,5,4,3,2,1
Arrays.sort(new Integer[]{7, 6, 5, 4, 1, 2, 3}, 4, 6, (a, b) -> {
    if (a > b) {
        return -1;
    } else if (a == b) {
        return 0;
    } else {
        return 1;
    }
});

对于使用比较器的场景,因为比较器是一个泛型方法,所以无法处理元素类型是基本类型的数组,在使用时要注意。

使用Collections.sort方法

JDK提供Collections.sort方法用于实现集合元素排序。查看该方法源码如下:

// Collections.java
public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

查看源码可知,Collections.sort方法只能用于List。所以,对于Set或Map,则需先先将其转换成List方可使用。进一步深入List源码实现:

// List.java
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}
// Arrays.java
public static <T> void sort(T[] a, Comparator<? super T> c) {
    if (c == null) {
        sort(a);
    } else {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, c);
        else
            TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
}

List在实现排序时,复用了数组的排序。而数组在实现排序时,使用归并排序实现元素排序。这里不再进一步深入归并排序的源码,有兴趣的同学可以自行查看相关源码。
接下来将给出使用示例:

List<Integer> originalList = new ArrayList<>();
originalList.add(1);
originalList.add(100);
originalList.add(50);
originalList.add(20);
originalList.add(35);
originalList.add(2);
Collections.sort(originalList, Integer::compareTo);
// 排序后,List的值为:
// [1, 2, 20, 35, 50, 100]

由于Integer实现了Comparable接口,所以上面的代码还可以进一步简写成Collections.sort(originalList)。当不指定比较器时,该方法会调用集合中元素所属类实现的排序方法。
进一步分析,这里并没有指定是升序还是降序时,可以看到当不指定生效还是降序时,默认是升序。这与数据库的默认排序规则是一样的。但在实际的应用中,有些场景也需要降序。对于List,Collections并不支持指定降序,有两种方法可以实现,一种是实现一个降序的比较器,另一种则是将这个已升序的数组进行翻转。这里给出Collections提供基于翻转实现降序的示例:

List<Integer> originalList = new ArrayList<>();
originalList.add(1);
originalList.add(100);
originalList.add(50);
originalList.add(20);
originalList.add(35);
originalList.add(2);
Collections.sort(originalList, Integer::compareTo);
Collections.reverse(originalList);
// 排序后,List的值为:
// [100, 50, 35, 20, 2, 1]

使用Stream.sorted方法

Java 8 新增流处理能力,可以使用Stream.sorted方法实现集合中元素排序。示例代码如下:

List<Integer> originalList = new ArrayList<>();
originalList.add(1);
originalList.add(100);
originalList.add(50);
originalList.add(20);
originalList.add(35);
originalList.add(2);
List<Integer> sortedList = originalList.stream().sorted(Integer::compareTo).collect(Collectors.toList());
// 排序后,List的值为:
// [1, 2, 20, 35, 50, 100]

多字段排序

上面讨论的是单字段的排序,适用集合中存储的元素是Integer、Long、String等基本类型的包装器类型场景,对于元素是对象场景,特别需要对对象中多个字段进行排序时,其处理略有不同。
对多字段排序,本质上是实现一个多字段的比较器。假设要从候选人中选取合格者,候选人定义如下:

class Candidate {
    int id;
    int programGrade;
    int technologyGrade;

    public Candidate(int id, int programGrade, int technologyGrade) {
        this.id = id;
        this.programGrade = programGrade;
        this.technologyGrade = technologyGrade;
    }

    public int getTechnologyGrade() {
        return this.technologyGrade;
    }

    public int getProgramGrade() {
        return this.programGrade;
    }

    public int getId() {
        return this.id;
    }
}

对候选人,假设有如下选取规则: 优选选取技术面试分数较高者;如果技术面试分数相同,则优先录取编程考试分数较高者;如果编程考试分数还相同,则录取id较小者。分析该应用场景,需要对多字段进行排序,具体来说,优先基于技术面试分数倒排,然后基于编程考试分数倒排,最后基于id升序排序(id一定不同)。使用Collections.sort和Stream.sorted分别实现该场景排序,示例代码如下:

public static void sortCandidateByCollections(List<Candidate> candidateList) {
    Collections.sort(candidateList, (o1, o2) -> {
        if (o1.technologyGrade != o2.technologyGrade) {
            return o2.technologyGrade - o1.technologyGrade;
        }
        if (o1.programGrade != o2.programGrade) {
            return o2.programGrade - o1.programGrade;
        }
        return o1.id - o2.id;
    });
}

public static List<?> sortCandidateByStream(List<Candidate> candidateList) {
    return candidateList.stream().sorted((o1, o2) -> {
        if (o1.technologyGrade != o2.technologyGrade) {
            return o2.technologyGrade - o1.technologyGrade;
        }
        if (o1.programGrade != o2.programGrade) {
            return o2.programGrade - o1.programGrade;
        }
        return o1.id - o2.id;
    }).collect(Collectors.toList());
}

public static List<?> sortCandidateByStreamInSimple(List<Candidate> candidateList) {
    return candidateList.stream().sorted(
            Comparator.comparing(Candidate::getTechnologyGrade)
                .thenComparing(Candidate::getProgramGrade)
                .thenComparing(Candidate::getId))
        .collect(Collectors.toList());
}

Collections.sort和Stream.sorted对比

从升序、降序,单字段、多字段场景,可以看到Collections.sort和Stream.sorted本质上都是对比较器的使用。但是两者在使用上有差异:
(1) Collections.sort会改变原来的数据,Stream.sorted不会改变原来的数据。如果期望Stream.sorted生成新的数据,还需另外处理。
(2) Collections.sort是静态方法,Stream.sorted是实例方法,两个方法的生命周期不同。
(3) Stream.sorted提供更多的简洁写法,且多数据场景下,支持并发处理。

过滤

除了排序,还有一种场景就是从集合中过滤出感兴趣的数据(也可理解成过滤掉不感兴趣的数据)。Java 8 之前通过遍历集合的方式实现,Java 8 之后(包含Java 8),可以使用Stream的filter方法实现。示例代码如下:

public static List<String> filterByForEach(List<String> languageList) {
    List<String> result = new ArrayList<>();
    for (String language : languageList) {
        if ("java".equals(language)) {
            result.add(language);
        }
    }
    return result;
}

public static List<String> filterByStream(List<String> languageList) {
    return languageList.stream()
            .filter(line -> !"java".equals(line))
            .collect(Collectors.toList());
}

参考

https://blog.csdn.net/w727655308/article/details/109959749 Java 实现多字段排序
https://juejin.cn/post/7083318717748084743 Java stream 多字段排序踩坑
https://segmentfault.com/a/1190000020158145 Java8 Streams filter 使用
https://blog.csdn.net/ssjdoudou/article/details/107886461 Java中Arrays.sort()的三种常用用法(自定义排序规则)
https://www.cnblogs.com/SupremeBoy/p/12717532.html Arrays.sort()详解

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值