摘要
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<Integer> {
//比較兩個數值
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<String>, 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<B>{
int comparaTo(B b){
//return的值按照自己是想怎么排序進行返回
return
}
}
當我們的類實現了Comparable接口,那么類對象集合,或者數組可使用工具類進行排序。
可以使用
Arrays.sort(數組,null),Collections.sort(集合,null)進行排序
Comparator
comparator一般是外比較器,以匿名內部類的形式存在,對數據進行比較
可新建一個比較器的對象,實現比較器內部的compara方法
//新建一個比較器
new Comparator<Integer>(){
@Override
public int compare(Integer str1, Integer str2){
return str1.compareTo(str2);
}
};
可使用Arrays.sort(數組,比較器)或者Collections.sort(集合,比較器)進行排序
Collections.sort()
了解了比較器,我們看一下一些工具類內到底是如何將數組進行排序的
我們舉個栗子,跟蹤源碼看一下
模擬數據
10 14 7 8 12
ArrayList<Integer> 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<Integer>(){
@Override
public int compare(Integer str1, Integer str2){
return str1.compareTo(str2);
}
});
開始調用
我們點進源碼看一下
1.第一步
這是Collections的sort方法,調用了list.sort方法,
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
2.第二步
我們點進list.sort()方法,這里他將集合數據轉成了數組傳入后面。數組默認長度是10,但是只有size個數據。但是list是個接口,所以我們查看其中一個實現類的實現方法,ArrayList的實現方法,
//這是ArrayList的sort方法。
public void sort(Comparator<? super E> 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 <T> void sort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> 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 <T> void sort(T[] a, int lo, int hi, Comparator<? super T> 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 <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
Comparator<? super T> 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 <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];
// 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看的,這樣數據改變更方面看一些,這里面我比較喜歡,是在數據排序的時候,這個是直接插入法,但是它優化了一點是,插入不是一個一個比較,而是和有序序列的中間值比較,這樣更快。看完之后我對比較器的理解更深了一層。