Java中的排序指南

720 阅读2分钟

学习如何使用比较器、可比较器和新的lambda表达式对一个JavaSetListMap 的原始类型和自定义对象进行排序。我们还将学习升序和降序的排序方法。

//Sorting an array
Arrays.sort( arrayOfItems );
Arrays.sort( arrayOfItems, Collections.reverseOrder() );
Arrays.sort(arrayOfItems, 2, 6);
Arrays.parallelSort(arrayOfItems);

//Sorting a List
Collections.sort(numbersList);
Collections.sort(numbersList, Collections.reverseOrder());

//Sorting a Set
Set to List -> Sort -> List to Set
Collections.sort(numbersList);

//Sorting a Map
TreeMap<Integer, String> treeMap = new TreeMap<>(map);

unsortedeMap.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByValue())
    .forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));

//Java 8 Lambda
Comparator<Employee> nameSorter = (a, b) -> a.getName().compareToIgnoreCase(b.getName());
Collections.sort(list, nameSorter);

Collections.sort(list, Comparator.comparing(Employee::getName));

//Group By Sorting
Collections.sort(list, Comparator
                        .comparing(Employee::getName)
                        .thenComparing(Employee::getDob));

1.对一个对象列表进行排序

要对一个对象列表进行排序,我们有两种流行的方法,即比较器和比较器接口。在接下来的例子中,我们将以不同的方式对一个Employee 对象的集合进行排序。

public class Employee implements Comparable<Employee> {

    private Long id;
    private String name;
    private LocalDate dob;
}

1.1.可比接口

[Comparable](https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html) 接口可以实现它的类进行自然排序。这使得这些类与它的其他实例具有可比性。

实现Comparable 接口的类必须覆盖compareTo() 方法,在该方法中,它可以指定同一类的两个实例之间的比较逻辑。

  • 实现了Comparable接口的对象的列表(和数组)可以通过Collections.sort()Arrays.sort() API自动进行排序。
  • 实现该接口的对象在被放入一个排序的地图(作为键)或排序的集合(作为元素)时将被自动排序。
  • 强烈建议(尽管不是必须)自然排序与[equals()](https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/) 方法一致。几乎所有实现Comparable 的Java核心类的自然排序都与equals() 一致。
ArrayList<Employee> list = new ArrayList<>();

//add employees..

Collections.sort(list);

要对列表进行反向排序,最好的方法是使用强加反向排序的*Comparator.reversed()*API。

ArrayList<Employee> list = new ArrayList<>();

//add employees..

Collections.sort(list, Comparator.reversed());

1.2.比较器接口

当不寻求自然排序时,我们可以借助于比较器接口来应用自定义的排序行为。

Comparator ,不需要修改类的源代码。我们可以在一个单独的类中创建比较逻辑,该类实现了Comparator 接口并重写了其compare() 方法。

在排序过程中,我们将这个比较器的一个实例和自定义对象的列表一起传递给sort() 方法。

例如,我们想按照员工的名字来排序,而自然排序已经通过id 字段实现。因此,为了对姓名字段进行排序,我们必须使用比较器接口编写自定义排序逻辑。

import java.util.Comparator;

public class NameSorter implements Comparator<Employee>;
{
    @Override
    public int compare(Employee e1, Employee e2)
    {
        return e1.getName().compareToIgnoreCase( e2.getName() );
    }
}

请注意,在给定的例子中,NameSorter在*sort()*方法中作为第二个参数使用。

ArrayList<Employee> list = new ArrayList<>();

//add employees to list

Collections.sort(list, new NameSorter());

要进行反向排序,我们只需要在比较器实例上调用*reversed()*方法。

ArrayList<Employee> list = new ArrayList<>();

//add employees to list

Collections.sort(list, new NameSorter().reversed());

1.3.用Lambda表达式进行排序

兰姆达表达式有助于在飞行中编写Comparator 实现。我们不需要创建一个单独的类来提供一次性的比较逻辑。

Comparator<Employee> nameSorter = (a, b) -> a.getName().compareToIgnoreCase(b.getName());

Collections.sort(list, nameSorter);

1.4.按组排序

为了在不同字段的对象集合上应用SQL风格的排序**(group by sort**),我们可以在一个链中使用多个比较器。这种比较器的连锁可以使用*Comparator.comparing()Comparator.thenComparing()*方法创建。

例如,我们可以按姓名对雇员列表进行排序,然后再按年龄排序。

ArrayList<Employee> list = new ArrayList<>();

//add employees to list

Collections.sort(list, Comparator
                        .comparing(Employee::getName)
                        .thenComparing(Employee::getDob));

2.对一个数组进行排序

使用java.util.Arrays.sort() 方法以各种方式对一个给定的数组进行排序。*sort()*是一个重载方法,它接受各种类型作为方法参数。

这个方法实现了双支点Quicksort排序算法,在所有数据集上提供了O(n log(n))的性能,通常比传统的(单支点)Quicksort实现要快。

2.1.升序排序

使用*Arrays.sort()*方法对一个整数阵列进行升序排序的Java程序。

//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, ... };

//Sort the array
Arrays.sort(numbers);

2.2.降序排序

Java提供了Collections.reverseOrder() 比较器,可以在一行中逆转默认的排序行为。我们可以使用这个比较器对数组进行降序排序。

注意,数组中的所有元素必须通过指定的比较器进行相互比较

//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, ... };

//Sort the array in reverse order
Arrays.sort(numbers, Collections.reverseOrder());

2.3.阵列范围的排序

Arrays.sort() 方法是一个重载方法,需要两个额外的参数,即:fromIndex (包括)和toIndex (排除)。

当提供上述参数时,数组将在所提供的范围内进行排序,从位置fromIndex 到位置toIndex

下面是一个将数组从元素9到18排序的例子,即{9, 55, 47, 18}将被排序,其余的元素不会被触及。

//Unsorted array
Integer[] numbers = new Integer[] { 15, 11, 9, 55, 47, 18, 1123, 520, 366, 420 };

//Sort the array
Arrays.sort(numbers, 2, 6);

//Print array to confirm
System.out.println(Arrays.toString(numbers));

程序输出。

[15, 11, 9, 18, 47, 55, 1123, 520, 366, 420]

2.4.并行排序

Java 8为并行处理数据集和数据流引入了很多新的API。其中一个API是Arrays.parallelSort()

parallelSort() 方法将数组分成多个子数组,每个子数组在不同的线程中用Arrays.sort() 进行排序。最后,所有排序的子数组被合并为一个排序的数组。

parallelSort()sort() ,这两个API的输出最后会是一样的。这只是一个利用Java并发性的问题。

//Parallel sort complete array
Arrays.parallelSort(numbers);

//Parallel sort array range
Arrays.parallelSort(numbers, 2, 6);

//Parallel sort array in reverse order
Arrays.parallelSort(numbers, Collections.reverseOrder());

3.对一个集合进行排序

在Java中没有直接支持对Set 的排序。要对一个Set 进行排序,请遵循以下步骤。

  1. Set 转换为List
  2. 使用Collections.sort() API对List 进行排序。
  3. List 转换回Set
//Unsorted set
HashSet<Integer> numbersSet = new LinkedHashSet<>(); //with Set items

List<Integer> numbersList = new ArrayList<Integer>(numbersSet) ;        //set -> list

//Sort the list
Collections.sort(numbersList);

//sorted set
numbersSet = new LinkedHashSet<>(numbersList);          //list -> set

4.对地图进行排序

一个Map 是键值对的集合。因此,从逻辑上讲,我们可以用两种方式对地图进行排序,即按键排序按值排序

4.1.按键排序

最好和最有效的按键排序的方法是将所有的地图条目添加到TreeMap对象中。TreeMap ,总是以排序的顺序存储键。

HashMap<Integer, String> map = new HashMap<>();   //Unsorted Map

TreeMap<Integer, String> treeMap = new TreeMap<>(map);   //Sorted by map keys

4.2.按值排序

Java 8中,Map.Entry 类有static 方法**comparingByValue()**来帮助我们对Map 的值进行排序。

comparingByValue() 方法返回一个比较器,该比较按自然顺序对Map.Entry 的值进行比较。

HashMap<Integer, String> unSortedMap = new HashMap<>(); //Unsorted Map

//LinkedHashMap preserve the ordering of elements in which they are inserted
LinkedHashMap<Integer, String> sortedMap = new LinkedHashMap<>();

unSortedMap.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByValue())
    .forEachOrdered(x -> sortedMap.put(x.getKey(), x.getValue()));

5.5.总结

在上面给出的例子中,我们学习了对数组、列表、地图和集合进行排序。

我们看到了初始化和使用Comparator 接口的不同方法,包括lambda表达式。我们还学会了如何有效地使用比较器接口。

学习愉快!!