一、简要介绍
Arrays里我们用的比较多的就是sort函数,这里我写一点我的学习过程。
sort函数本身的排序性能是比较高的,它会在不同情况下运用不同的排序方法,如快排、二叉排,它给出了默认的从小到大的排序,同时也提供了自定义的排序方法,这里我会从基本数据类型的排序和自己创建对象进行排序来说明。(JDK版本为11)
二、基本数据类型的默认排序
1. int型
基本代码
class sort1{
public static void main(String[] args) {
int[] help = new int[]{1,2,3,4,2,3,1,4,643,4};
Arrays.sort(help);
}}
这个的排序结果就是默认的从小到大排序,我们追sort的原码:
点击查看代码
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
发现它其实就是调用了一个sort(int[]a),其中调用了DualPivotQuicksort的sort方法,这个静态类的sort方法其实是很多种排序方法的一个综合,由于我是初学,这里不讲解
2. char型
我将上述代码里面的类型改为char,发现调用的还是同一个方法,这个方法的参数可以有很多种,基本上默认的数据类型都可以用,剩下的数据类型我就不一一举例。
点击查看代码
public static void sort(char[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
三、基本数据类型的自定义排序
Java里面的sort函数提供了一个Comparator接口使用户能够自定义排序顺序,如果需要自己定义排序顺序,需要实现一下Comparator接口,如图所示:
可以看到sort这里可以接受两个参数,第一个是待排序的数组,第二个是一个Comparator接口。
我们拿字符数组排序来举例子:
点击查看代码
public static void main(String[] args) {
Character[] help = new Character[]{'e','b','e','x','p','c','a'};
//Arrays.sort(help);
Arrays.sort(help,new Comparator<Character>(){
@Override
public int compare(Character o1, Character o2) {
return o1-o2;
}
});
}
这是一个完整的自定义排序程序,我们使用内部类的形式实现了Comparator接口,可以看到再实现了Comparator接口中我们要实现它的compare的方法,这个方法就是我们自定义排序的关键,它有两个参数,两个char类型的封装类型的变量,而我们的返回值是一个int型,这是为什么?
下面我们来详细说明:
1、为什么compare的参数是两个封装类型?
我们这里追一下源码:public static <T> void sort(T[] a, Comparator<? super T> c)
这是sort方法的真实样子,可以看见它利用范型,严格规定来传入参数的类型,这里我们可以大概看出为什么要传入对象类型,那这里我可以省略吗?当然可以!如果省略,这里默认你传入的是一个Object对象,代码如下:
点击查看代码
Arrays.sort(help,new Comparator(){
@Override
public int compare(Object o1, Object o2) {
return (Character)o1-(Character) o2;
}
});
可以看到我这里没有指定范型,o1和o2两个对象就是Objiect类型,则在返回的时候需要进行强制类型转换。因此在我们排序不同类型的序列时,应该传入对应的封装类型或者对象类型。这是自定义排序的基本条件,同时定义数组的时候也需要讲定义类型改变为封装类型,如:
Character[] help = new Character[]{'e','b','e','x','p','c','a'};
2、为什么我们的返回值是两个对象相减,这里我们继续追原码:
这是sort函数的全貌,可以看到,当我们传入的数组不为0时,真正起排序作用的是TimSort.sort(),因为sort会根据传入待排序数组的类型,长度等进行合适的选择,还有更多的__Sort对象含有这个sort方法,而我们的例子这里调用的是TimSort里面的sort方法
点击查看代码
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);
}
}
我们直接进入真正行使排序功能的代码:
点击查看代码
assert lo < hi;
int runHi = lo + 1;
if (runHi == hi)
return 1;
// Find end of run, and reverse range if descending
if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
runHi++;
reverseRange(a, lo, runHi);
} else { // Ascending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
runHi++;
}
return runHi - lo;
本文不关心如何排序,我们着重看compare方法起了什么作用,注意看这两句代码:
if (c.compare(a[runHi++], a[lo]) < 0)
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
可以看到这里直接调用了我们所实现的compare方法,用来进行排序里面的判断,这里真正的原理清楚了,原来我们返回的值可以直接影响到整个排序的进程,而返回值的正负是判断的关键,所以compare的返回值返回的是一个int型:
点击查看代码
public int compare(Character o1, Character o2) {
return o1-o2;
}
这里虽然返回的是两个char的封装对象相减,但在计算的时候会自动解封装,转换为两个实数相减,最后返回一个int型的变量,解封装源码:
点击查看代码
public char charValue() {
return value;
}
返回的int值的正负影响到真正的排列顺序,而我们想要改变排列顺序就很简单,转换一下o1和o2的顺序即可,让返回正数的情况返回负数。基本上自定义排序的原理就在于此,那我们是否可以自己来实现这样的原理,当然可以!下面我用冒泡排序来演示一下。
四、自己实现自定义排序的原理(利用冒泡排序)
点击查看代码
public static void main(String[] args) {
int []a = new int[]{1,2,3,2,3,4,52,3};
for(int i = 0; i < a.length; i++){
for(int j = 0; j < a.length-i-1;j++){
if(compare(a[j],a[j+1]) > 0){
int tem = a[j];
a[j] = a[j+1];
a[j+1] = tem;
}
}
}
for(int i = 0;i < a.length; i++){
System.out.print(a[i]+"\t");
}
}
public static int compare(int o1,int o2){
return o1-o2;
}
可以看到我们利用了我们自己实现的compare方法对冒泡排序中的判断条件进行了干预,造成了我们可以利用o1和o2的顺序来对最后的排序结果进行干预,核心就是下面的代码:
if(compare(a[j],a[j+1]) > 0)
public static int compare(int o1,int o2){ return o1-o2; }