java的比较器小结

摘要

java的比较器分两种,也即是内外比较器,内部比较器是comparable接口,外部比较器是comparator接口,为什么分内外比较器呢。

内部比较器:comparable,当需要对某个类的对象进行排序的时候,则需要该类实现comparable接口,重写comparaTo方法,实现这个接口的类的对象列表,可使用Array.sort或者Collections.sort进行排序,

外部比较器:comparator接口,匿名内部类的形式存在,重写内部的compare方法,可对对象数组或者集合使用Arrays.sort(数组,比较器)或者Collections.sort(集合,比较器)进行排序

compara和comparaTo方法共同之处,就是如果比较的数比被比较的数小返回-1,

例: a.comparaTo(b) a小,返回-1 ,a大返回1,compara同理

可以查看我的博客
https://itzmn.github.io/2018/11/16/java%E7%9A%84%E6%AF%94%E8%BE%83%E5%99%A8%E5%B0%8F%E7%BB%93/

Comparable

comparaTo方法

类实现comparable接口,都要重写comparaTo方法,举个栗子,

Integer类中的方法

//Integer实现了Comparable接口
public final class Integer extends Number implements Comparable {

//比较两个数值
public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
}
//比较两个数值,如果前面一个大,返回1,小返回-1,相等返回0
public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

String类中

//实现了接口
public final class String
    implements java.io.Serializable, Comparable, CharSequence {
    
    //String类中的compareTo方法
public int compareTo(String anotherString) {
        //得到调用者的值的长度
        int len1 = value.length;
        //得到比较的值的长度
        int len2 = anotherString.value.length;
        //查看最小长度
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        //循环到最小长度的值结束
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            //比较每一个字符
            if (c1 != c2) {
                //返回字符的ASCII码差值
                return c1 - c2;
            }
            k++;
        }
        //如果前面的字符都一样,返回长度差
        return len1 - len2;
    }

以上是java内置好的一些类,实现了接口,重写了方法,如果我们自己类想要实现接口,可以重写这个方法,自定义排序的方法

例:

 Class B implements Comparable{
     
     int comparaTo(B b){
        //return的值按照自己是想怎么排序进行返回
         return 
     }
 }

当我们的类实现了Comparable接口,那么类对象集合,或者数组可使用工具类进行排序。

可以使用

Arrays.sort(数组,null),Collections.sort(集合,null)进行排序

Comparator

comparator一般是外比较器,以匿名内部类的形式存在,对数据进行比较

可新建一个比较器的对象,实现比较器内部的compara方法

//新建一个比较器
new Comparator(){
            @Override
            public int compare(Integer str1, Integer str2){
                return str1.compareTo(str2);
            }
};

可使用Arrays.sort(数组,比较器)或者Collections.sort(集合,比较器)进行排序

Collections.sort()

了解了比较器,我们看一下一些工具类内到底是如何将数组进行排序的

我们举个栗子,跟踪源码看一下

模拟数据

10 14 7 8 12

ArrayList integers = new ArrayList<>();
        integers.add(10);
        integers.add(14);
        integers.add(7);
        integers.add(8);
        integers.add(12);
        integers.add(3);
        Collections.sort(integers, new Comparator(){
            @Override
            public int compare(Integer str1, Integer str2){

                return str1.compareTo(str2);
            }
        });

开始调用

我们点进源码看一下

1.第一步

这是Collections的sort方法,调用了list.sort方法,

@SuppressWarnings({"unchecked", "rawtypes"})
    public static  void sort(List list, Comparator c) {
        list.sort(c);
    }

2.第二步

我们点进list.sort()方法,这里他将集合数据转成了数组传入后面。数组默认长度是10,但是只有size个数据。但是list是个接口,所以我们查看其中一个实现类的实现方法,ArrayList的实现方法,

//这是ArrayList的sort方法。
public void sort(Comparator c) {
        final int expectedModCount = modCount;
    //调用了Arrays.sort 并将比较器传了进来,
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
  }

3.第三步

我们点进Arrays.sort方法查看,我们一条一条看

public static  void sort(T[] a, int fromIndex, int toIndex,
                                Comparator 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);
        }
    }

1.如果比较器为null,这个我们一会再看,(记住这个,我们会回来的)

2.rangeCheck(),看名字就可以看出,这是一个检验下标是否越界的方法,就不细说了,

3.下面这个判断,我也不大懂,但是每次他都是进入了else进行排序

4.第四步

我们进入TimSort.sort方法,传入的数据是 a:数组数据,也就是集合中的数据,formIndex:0,toIndex:是刚刚集合的size。

 static  void sort(T[] a, int lo, int hi, Comparator c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

//下面有些代码,都没看了,就看到这
 .....
 }

我们还是一行一行看

1.断言,判断下标是否正确

2.得到集合的数据长度,并判断是否小于2,也即是判断有没有排序的必要

3.如果大于,判断数据是否小于32,我们看的是小于,大于的代码没看

4.int initRunLen = countRunAndMakeAscending(a, lo, hi, c);

我们看一下这个方法是干什么的

下面点进入,贴代码

4.1 countRunAndMakeAscending代码
//传入的参数,a是数组,lo是开始位置默认是0,hi是结束位置默认是刚刚集合的长度
private static  int countRunAndMakeAscending(T[] a, int lo, int hi,
                                                    Comparator c) {
        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;
    }

解释一下方法的参数

a是数组,lo是开始位置默认是0,hi是结束位置默认是刚刚集合的长度(下面称集合长度),c是比较器

1.断言,开始索引小于传入的集合长度

2.runHi变成了开始坐标加一

3.如果这个数值和集合长度相同,则返回1,

4.判断,并且将runHi加1,在这里我们刚刚在比较器内部的操作效果就显示出来了。在这里,我们将坐标靠后面的数与该坐标数值进行比较,如果这里比较用的就是我们自己写在比较器里面的比较

5.如果返回-1 ,说明后面一个值小,这两个数构成了降序,然后还是接着判断后面一个值和当前的值大小,直到降序被打断,记录这个坐标,一会返回这个坐标。

如果被打断,进入另一个方法,将这一段降序的数组,反转,改成升序,

// 反转这部分的数组
private static void reverseRange(Object[] a, int lo, int hi) {
        hi--;
        while (lo < hi) {
        //交换数组的值
            Object t = a[lo];
            a[lo++] = a[hi];
            a[hi--] = t;
        }
    }

6.如果返回大于0,说明后面一个值大,这两个值构成了升序。然后继续比较runHi位置和前面一个位置的数值,如果都是后面的大,就一直向后,直到数值变化为小的,也即是一直是升序,突然升序变了,记录下这个位置并返回这个位置。

4.1结束,回到上一个方法

5,第五步

我们返回了一个坐标,现在这个数组的开始到这个坐标以前,已经满足了递增的规律,然后我们需要将整个数据进行排序。进入了

binarySort(a, lo, hi, lo + initRunLen, c);方法

// 传入数组,开始坐标,结束坐标,start是上一步,0加上已经排序好的坐标的后一位
private static  void binarySort(T[] a, int lo, int hi, int start,
                                       Comparator c) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            T pivot = a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
            
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (c.compare(pivot, a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

            int n = start - left;  // The number of elements to move
           
            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;
        }
    }

首先我们看一下传入的参数

// 传入数组,lo开始坐标,hi结束坐标,start是上一步,0加上已经排序好的坐标的后一位

也就是说现在数组有hi个数据,但是从lo到start的数据是已经满足递增的数据了,前面已经看过了,现在我们需要将后面的数据和前面的数据汇合,形成整个递增。

1.如果start==lo,说明前面没有递增数据,所以将坐标加1,为后面准备

2.循环,start以后都是尚未排序的,我们需要将后面的数据插入到前面的有序序列,

3.保存当前位置的数值,

4.得到已经递增的数据的左坐标,也就是lo,得到递增数据的右坐标也即是start

5.然后将当前位置的值与递增数据的中间位置的数值进行比较,如果小于,那么说明当前值是插在递增数据的前半段,将递增数据的右坐标改成中间位置的坐标,如果是大于,那么是后半段,将递增数据的左坐标改成中间位置,直到找到正确位置,判断当前值的位置与正确位置的差,如果小与2则进行移动,如果大于则进行拷贝

6.循环将以上数值全部弄完,这样我们就可以得到整个有序数列了。

我们现在回到第三步的第一小步,如果比较器为null,

进入sort(a, fromIndex, toIndex);

3.1

 //对没有比较器的数组进行比较
 public static void sort(Object[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, fromIndex, toIndex);
        else
            ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
    }

3.2

//进入了ComparableTimSort的sort方法 ,传入数组,起始坐标与结束坐标。
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
        assert a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi);
            binarySort(a, lo, hi, lo + initRunLen);
            return;
        }

这里面的几步如

int initRunLen = countRunAndMakeAscending(a, lo, hi);

binarySort(a, lo, hi, lo + initRunLen);

都与上面的几步差不多,不过内部的比较方法不一样

private static int countRunAndMakeAscending(Object[] a, int lo, int hi) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;

        // Find end of run, and reverse range if descending
        if (((Comparable) a[runHi++]).compareTo(a[lo]) < 0) { // Descending
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              // Ascending
            while (runHi < hi && ((Comparable) a[runHi]).compareTo(a[runHi - 1]) >= 0)
                runHi++;
        }

        return runHi - lo;
    }

因为比较器为null,所以会将值转成比较器进行比较,存储的值,都是实现了Comparable接口的类,内部的比较方法也都重写了,会按照自带的比较。

在最后的比较阶段也是一样的。

private static void binarySort(Object[] a, int lo, int hi, int start) {
        assert lo <= start && start <= hi;
        if (start == lo)
            start++;
        for ( ; start < hi; start++) {
            Comparable pivot = (Comparable) a[start];

            // Set left (and right) to the index where a[start] (pivot) belongs
            int left = lo;
            int right = start;
            assert left <= right;
           
            while (left < right) {
                int mid = (left + right) >>> 1;
                if (pivot.compareTo(a[mid]) < 0)
                    right = mid;
                else
                    left = mid + 1;
            }
            assert left == right;

          
            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;
        }
    }

Arrays.sort

Arrays.sort在上面已经被调用过了,可以看一下

这样我们就把比较器的代码大致看完了 ,看这个源码,我是传数据debug看的,这样数据改变更方面看一些,这里面我比较喜欢,是在数据排序的时候,这个是直接插入法,但是它优化了一点是,插入不是一个一个比较,而是和有序序列的中间值比较,这样更快。看完之后我对比较器的理解更深了一层。

你可能感兴趣的:(java的比较器小结)