JAVA之定制排序Comparator接口
前言
JAVA之Arrays类定制排序、自己实现定制排序、Treeset、Treemap有序集合
Array类是JAVA内置的一个类,如其名主要是针对数组,提供了一系列的静态方法,此处我们主要记录一下关于sort函数的定制排序。
一、Arrays类sort方法自然排序
1.Arrays类
Arrays 类是 Java 标准库中的一个实用类,它位于 java.util 包中,提供了大量用于操作数组的静态方法。这些方法涵盖了数组的常见操作,例如排序、搜索、比较、填充、转换等。
2.Arrays.sort(array)方法
该类的sort方法是对数组进行排序,类里面,对该方法进行了重写,一种是只有数组,没有参数的方法,如下面的代码所示。另一种是传入匿名内部类的方法。
代码如下(示例):
package com.cgq.socket;
import java.util.Arrays;
/**
* @author 陈小陈
* @vesion 1.0
*/
public class test {
public static void main(String[] args) {
Integer arr[] = {1,-1,7,0,89};
Arrays.sort(arr);
for (Integer i : arr){
System.out.print(i+" ");
}
}
}
自然排序比较简单,直接调用Arrays的静态方法,传入数组类型的参数,可以根据输出结果看出来,这个函数是对数组本身进行排序,而并非创造一个新的数组。
3.Arrays.sort(array, Comparator)定制排序(源码剖析)
我们先看sort的源码:
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
sort传入的形参是一个Object的数组,然后有个if-else语句。我们继续看关于LegacyMergeSort的代码。
static final class LegacyMergeSort {
private static final boolean userRequested =
java.security.AccessController.doPrivileged(
new sun.security.action.GetBooleanAction(
"java.util.Arrays.useLegacyMergeSort")).booleanValue();
}
关于if语句后执行的是旧版的合并排序(legacy merge sort)。在某些情况下,用户可能会通过系统属性或者其他方式请求使用老的排序算法。这是为了兼容某些情况下老版本的行为。
假设你确实需要使用旧的合并排序算法,你可以通过设置系统属性来请求使用 legacyMergeSort:
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
接下来是定制排序相关的内容;
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);
}
}
我们先来看一下Comparator是什么,根据源码可以看Comparator是一个接口。而且该接口有一个compare方法,没有方法体,只定下了两个参数。
我们通过匿名内部类的方法,实现这个接口,传入一个具体的对象。
package com.cgq.socket;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* @author 陈小陈
* @vesion 1.0
*/
public class test {
public static void main(String[] args) {
Integer arr[] = {1,-1,7,0,89};
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (Integer i : arr){
System.out.print(i+" ");
}
}
}
这样就实现了从大到小的排序。
关于为什么是从大到小的,我们需要继续看源码,前面看到了调用private static void binarySort(T[] a, int lo, int hi, int start,Comparator<? super T> c)这个方法,该方法是Java 中使用的底层排序算法,是一种归并排序和插入排序的混合体,能够高效处理各种数据分布的场景。当排小数组时直接用简单的插入排序,大数组则是归并排序和插入排序的结合体。
这里的排序都是插入排序实现的,这里的插入排序又结合了二分查找,核心代码如下。其中c是Comparator接口对应的变量,动态绑定,调用其运行类型,也就是我们传入的compare方法。
就是这行代码if (c.compare(pivot, a[mid]) < 0)决定了我们从高到低还是从低到高进行排序。pivot是要插入的数o1,a[mid]o2,如果我们return的是o2-o1。这里举一个例子吧,数组有两个数,第一个为8,第二个为10。
下面代码a[2],lo=0,hi=1,start = 0。start是插入的起始,从lo到hi都要插入,通过for循环实现的。start++后,start=1,mid=left=right=0。o1 = pivot=a[start] = 10,o2 = a[mid]=8,return o2-o1=-2<0,执行if语句下面代码right=mid=0, n = start -left = 1,n是需要后移的数组数量,如果变成return o1-o2就会有完全不同的效果了。
private static <T> void binarySort(T[] a, int lo, int hi, int start,
Comparator<? super T> c) {
assert lo <= start && start <= hi;
if (start == lo)
start++;
for ( ; start < hi; start++) {
T pivot = a[start]; //从statr等于0开始插入
// Set left (and right) to the index where a[start] (pivot) belongs
int left = lo;
int right = start;
assert left <= right;
/*
* Invariants:
* pivot >= all in [lo, left).
* pivot < all in [right, start).
*/
while (left < right) {
int mid = (left + right) >>> 1;
if (c.compare(pivot, a[mid]) < 0)
right = mid;
else
left = mid + 1;
}
assert left == right;
/*
* The invariants still hold: pivot >= all in [lo, left) and
* pivot < all in [left, start), so pivot belongs at left. Note
* that if there are elements equal to pivot, left points to the
* first slot after them -- that's why this sort is stable.
* Slide elements over to make room for pivot.
*/
int n = start - left; // The number of elements to move
// Switch is just an optimization for arraycopy in default case
switch (n) {
case 2: a[left + 2] = a[left + 1];
case 1: a[left + 1] = a[left];
break;
default: System.arraycopy(a, left, a, left + 1, n);
}
a[left] = pivot;
}
}
3.自己实现一下定制排序
package com.cgq.socket;
import java.util.Comparator;
/**
* @author 陈小陈
* @vesion 1.0
*/
public class test {
public static void main(String[] args) {
person[] p = new person[4];
p[0] = new person("bob",44);
p[1] = new person("Thrump",49);
p[2] = new person("david",22);
p[3] = new person("john",34);
for(person a :p){
System.out.print(a);
}
System.out.println();
bubble_age(p, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
person p1 =(person) o1;
person p2 =(person) o2;
return p1.name.length() - p2.name.length();
}
});
for(person a :p){
System.out.print(a);
}
System.out.println();
bubble_age(p, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
person p1 =(person) o1;
person p2 =(person) o2;
return p2.age - p1.age;
}
});
for(person a :p){
System.out.print(a);
}
}
//普通版本的排序
public static void bubble_age(person[] p){
person temp = null;
for(int i = 0; i < p.length-1;i++){
for(int j = 0;j < p.length- i - 1;j++){
if(p[j].age > p[j+1].age){
temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
}
}
}
}
//冒泡定制一下
public static void bubble_age(person[] p , Comparator c){
person temp = null;
for(int i = 0; i < p.length - 1;i++){
for(int j = 0;j < p.length- i - 1;j++){
if(c.compare(p[j],p[j+1])>0){
temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
}
}
}
}
}
//对一个类进行排序
class person{
String name;
int age;
public person(String name, int age){ //构造器
this.name = name;
this.age = age;
}
@Override
public String toString() { //重写toString
return this.name + " " +this.age+" ";
}
}
未排序前及两次排序后的结果如下图所示。
当然一般情况下,我们调用Arrays.sort()简单省事。
二、Treeset和Treemap
Java里常用的集合有List,可重复有顺序,还有set和map,map集合存放键值对K-V,而set把V设置成常量,只有变换的key,常用的Hashmap和Hashset都是无序的,都是利用哈希表+链表+红黑树实现的。而利用Tree实现的set和map可以保证有序性。这里利用了二叉排序树的性质再结合Comparator接口实现的。
这里举一个set的例子即可,map也是对key排序。
package com.cgq.socket;
package com.cgq.socket;
import java.util.Comparator;
import java.util.TreeSet;
/**
* @author 陈小陈
* @vesion 1.0
*/
public class tttt {
public static void main(String[] args) {
TreeSet t1 = new TreeSet(); //调用无参构造函数
TreeSet t2 = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
t1.add("John");
t1.add("bob");
t1.add("david");
t1.add("aaaaaaaaa");
System.out.println(" t1 "+t1);
t2.add("John");
t2.add("bob");
t2.add("david");
t2.add("aaaaaaaaa");
System.out.println(" t2 "+t2);
}
}
我们可以通过上面的输出结果看出,如果调用无参构造的TreeSet是完全无序的,我们需要调用重载的另一种构造函数。
这里可以发现Comparator接口,下面是add函数上的核心代码。可以看出通过,Comparator接口接收到的对象调用compare方法的返回值,来确定排序,然后放到左子树和右子树。感兴趣的话可以自己追一下源码。