1.内部实现:Comparable

  • 排序对象通过实现 Comparable 接口的抽象方法:int compareTo(Object o),完成对象排序的操作
  • 实现 Comparable 的类必须实现 compareTo(Object o) 方法,两个对象即通过 compareTo(Object o) 方法的返回值来比较大小。
  1. 如果当前对象 this 大于形参对象 o ,则返回正整数,
  2. 如果当前对象 this 小于形参对象 o ,则返回负整数,
  3. 如果当前对象 this 等于形参对象 o ,则返回零。
  • 实现 Comparable 接口的类的对象数组(和有序集合)可以通过 Arrays.sort(和 Collections.sort )进行自动排序。
  • Comparable的典型实现:(默认都是从小到大排序)

案例:String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,以String源码为例,进行讲解

  • String继承Comparable 接口


java comparator接口 排序算法_java 比较器


  • 重写compareTo方法:该方法实现了String的升序排序


java comparator接口 排序算法_comparator比较器用法_02


  • 测试结果
String[] testArray = new String[3];
testArray[0] = "a";
testArray[1] = "z";
testArray[2] = "b";
Arrays.sort(testArray);
for (String element : testArray) {
    System.out.println(element);
}
输出:
a
b
z


2.外部比较器:Comparator

  • 当元素的类型没有实现Comparable接口时或者实现了Comparable但是不满足当前的要求,那么可以考虑使用Comparator接口实现类来排序
  • Comparator 接口中只有两个抽象方法 int compare(Object o1, Object o2); 、boolean equals(Object obj); ,Comparator 接口实现类默认继承了 Object 类的 equals 方法,即间接实现了 equals 方法,因此只需实现 int compare(Object o1, Object o2) 即可。
  • 可以将 Comparator 接口实现类传递给 sort 方法(如 Arrays.sort 或 Collections.sort),从而允许在排序顺序上实现精确控制。
  • 重写 int compare(Object o1, Object o2) 方法,比较o1和o2的大小:
  1. 如果要按照升序排序,则 o1小于o2返回负整数,o1与o2相等返回0,01大于02返回正整数
  2. 如果要按照降序排序,则 o1小于o2返回正整数,o1与o2相等返回0,01大于02返回负整数
  • 代码演示,String实现Comparable完成的升序排序,现在希望String可以降序排序,实现Comparator完成
String[] testArray = new String[3];
testArray[0] = "a";
testArray[1] = "z";
testArray[2] = "b";
Arrays.sort(testArray, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        int len1 = o1.toCharArray().length;
        int len2 = o2.toCharArray().length;
        int lim = Math.min(len1, len2);
        char v1[] = o1.toCharArray();
        char v2[] = o2.toCharArray();
        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return -(c1 - c2);
            }
            k++;
        }
        return -(len1 - len2);
    }
});
for (String element : testArray) {
    System.out.println(element);
}

输出结果:
z
b
a


其实看了上面例子,可能仅仅对实现排序的接口有了一个映像,具体正负值和升降序有啥关系,可能看完也没明白,我当时在学习的时候也有这种感觉,下面再看一个例子

首先看下面代码:


测试用例1:compare直接返回1
String[] testArray = new String[3];
testArray[0] = "a";
testArray[1] = "z";
testArray[2] = "b";
Arrays.sort(testArray, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return 1;
    }
});
for (String element : testArray) {
    System.out.println(element);
}
输出:
a
z
b

测试用例2:compare直接返回0
String[] testArray = new String[3];
testArray[0] = "a";
testArray[1] = "z";
testArray[2] = "b";
Arrays.sort(testArray, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return 0;
    }
});
for (String element : testArray) {
    System.out.println(element);
}
输出:
a
z
b

测试用例3:compare直接返回-1
String[] testArray = new String[3];
testArray[0] = "a";
testArray[1] = "z";
testArray[2] = "b";
Arrays.sort(testArray, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return -1;
    }
});
for (String element : testArray) {
    System.out.println(element);
}
输出:
b
z
a


从运行结果发现,数组元素顺序未改变。

  • 在compare方法中,不管o1、o2对象的内容,统一放回负整数,运行结果发现,数组元素倒置。
  • 在compare方法中,不管o1、o2对象的内容,统一放回0,运行结果发现,数组元素顺序未改变。

在comparable接口中及集合中的运行结果一致。

总结:

  • 在调用 compare(Object o1, Object o2) 方法时,会把两个元素中索引较小的元素赋值给o2,索引较大的元素赋值给o1。Comparable接口中compareTo(Object o) 方法的调用者是两个元素中索引较大的元素,参数对象 o 则是索引较小的元素。(JDK版本不同,对应关系可能不同)。通过debug调试或查看源码也能得出这个结论。

比如,对于数组{5, 10, 20, 15},比较前两个元素调用 compare(Object o1, Object o2) 方法时,会把5赋值给o2,把10赋值给o1。

  • 按升序排序时,若o1 > o2,则返回正整数,不调整元素位置;若o1 == o2,则返回0,不调整元素位置;若o1 < o2,则返回负整数,调整元素位置。
  • 按降序排序时,若o1 > o2,则返回负整数,需要调整元素位置;若o1 == o2,则返回0,不调整元素位置;若o1 < o2,则返回正整数,不调整元素位置。

因此,不管是升序还是降序,只有在返回负整数时,才会调整元素的位置。

综上,排序时是否需要调整元素位置是由对象o1、o2与数组中元素的对应关系 和 比较方法返回值的正负共同决定的。

3.两者的区别

  • 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。
  • 用Comparator 的好处是不需要修改源代码, 而是在待比较对象的类的外部实现一个比较器, 当某个自定义的对象需要作比较的时候,把待比较对象和比较器一起传递过去就可以实现排序功能。
  • 像String类、包装类等JDK内置类实现了Comparable接口默认是升序排序,如果要降序排序或指定其他排序规则只能使用Comparator接口。

4.特殊案例:TreeMap按value排序导致丢数问题

问题描述:实践发现当map中存在value相同的两个元素时,比如元素(4,0.5)和(5,0.5),排完序后map中(4,0.5)丢失

问题原因:

TreeMap 使用了红黑树结构保存数据。喜欢磕一下红黑树细节的先看这个 红黑树实现分析 。

TreeMap put方法中的comparator部分的源码截图如下


java comparator接口 排序算法_comparable接口_03


标记1comparator.compare(k1, k2) 返回值小于0 ,则把值放在左子树,相当于排在前面.标记2comparator.compare(k1, k2) 返回值大于0 ,则把值放在右子树,相当于排在后面.标记3comparator.compare(k1, k2) 返回值等于0 ,把k1k2当成相同key,并替换值。这就造成了上图中数据丢失的情况 。

解决方法:不使用compareTo,自己手动比较,并将相等的时候返回1即可修复改问题

总结:TreeMap 自定义比较器,Comparator不能返回0 !!!compareTo()也不能返回0 !!!仅限于要比较的对象只用于TreeMap才要注意

5.题外话

Arrays.sort完成排序时,sort内部使用的排序时binarySort,查了下该方法是二分排序,该方法是结合二分查找+直接插入排序,直接插入排序时使用二分查找找到插入的位置,感兴趣的同学可以去看源码(在TimeSort类中的binarySort方法)

6.参考