comparator和comparable
前言
最近筆者被問到對象排序的時候,要求是傳入不同的規則,排序不一樣,類似一個按不同的條件排序的功能,筆者想到了comparator,其實comparable也是可以的,只是不太符合這個功能而已,通過comparator的切換即可實現。
comparable
comparable demo
comparable是一個接口,使用的bean實現即可,非常方便,缺點是與bean強藕聯。
public class User implements Comparable<User>{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(User o) {
return o.getAge() - this.getAge();
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
實現compareTo方法,一個對象是比較的其他對象,一個是對象本身,使用符號位判斷大小,即0與正負數。此示例表示按照User的age倒序排列,示例如下:
public static void main(String[] args) {
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setAge(11);
User user2 = new User();
user2.setAge(22);
users.add(user1);
users.add(user2);
Collections.sort(users);
System.out.println(users);
}
結果如下:
comparable源碼分析
Collections.sort,以list的排序爲例,本身是一個靜態方法,T泛型限制必須實現Comparable接口。
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
調用的list的sort方法,筆者是ArrayList,估計其他實現略有區別
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
調用Arrays.sort((E[]) elementData, 0, size, c);
ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
TimSort排序,雙軸快排,這裏就比較複雜了
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;
}
/**
* March over the array once, left to right, finding natural runs,
* extending short natural runs to minRun elements, and merging runs
* to maintain stack invariant.
*/
ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen);
int minRun = minRunLength(nRemaining);
do {
// Identify next run
int runLen = countRunAndMakeAscending(a, lo, hi);
// If run is short, extend to min(minRun, nRemaining)
if (runLen < minRun) {
int force = nRemaining <= minRun ? nRemaining : minRun;
binarySort(a, lo, lo + force, lo + runLen);
runLen = force;
}
// Push run onto pending-run stack, and maybe merge
ts.pushRun(lo, runLen);
ts.mergeCollapse();
// Advance to find next run
lo += runLen;
nRemaining -= runLen;
} while (nRemaining != 0);
// Merge all remaining runs to complete sort
assert lo == hi;
ts.mergeForceCollapse();
assert ts.stackSize == 1;
}
按照32的mini-TimSort劃分,無需merges,超過的需要二分查找插入排序。這裏就涉及真正的算法了,如果其他實現,可能不是這樣的。
comparator
comparator demo
comparator是函數式接口,只有一個實現方法,基於比較接口,好處是,可以動態的更換接口的多個實現。
public static void main(String[] args) {
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setAge(11);
User user2 = new User();
user2.setAge(22);
users.add(user1);
users.add(user2);
// Collections.sort(users);
Collections.sort(users, new MyCompare());
System.out.println(users);
}
private static class MyCompare implements Comparator<User> {
@Override
public int compare(User o1, User o2) {
return o2.getAge() - o1.getAge();
}
}
運行結果同理
但是我們可以寫多個實現接口的類。
Comparator源碼分析
函數式接口,很明顯的定義
@FunctionalInterface
public interface Comparator<T> {
sort方法,當然這個是集合工具類,我們可以自己實現排序外的邏輯
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
仍然
Arrays.sort((E[]) elementData, 0, size, c);
但是這裏c是有比較器的,意味着使用比較器來比較
TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
TimSort排序
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;
}
/**
* March over the array once, left to right, finding natural runs,
* extending short natural runs to minRun elements, and merging runs
* to maintain stack invariant.
*/
TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
int minRun = minRunLength(nRemaining);
do {
// Identify next run
int runLen = countRunAndMakeAscending(a, lo, hi, c);
// If run is short, extend to min(minRun, nRemaining)
if (runLen < minRun) {
int force = nRemaining <= minRun ? nRemaining : minRun;
binarySort(a, lo, lo + force, lo + runLen, c);
runLen = force;
}
// Push run onto pending-run stack, and maybe merge
ts.pushRun(lo, runLen);
ts.mergeCollapse();
// Advance to find next run
lo += runLen;
nRemaining -= runLen;
} while (nRemaining != 0);
// Merge all remaining runs to complete sort
assert lo == hi;
ts.mergeForceCollapse();
assert ts.stackSize == 1;
}
算法類似,只是new TimSort<>(a, c, work, workBase, workLen);,傳入了比較器
總結
其實很簡單的比較,推薦comparator,函數式編程,更靈活。這裏的比較算法使用的Arrays.sort非常複雜的算法,Arrays還可以並行排序,估計效率更高
public static <T> void parallelSort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> cmp) {
rangeCheck(a.length, fromIndex, toIndex);
if (cmp == null)
cmp = NaturalOrder.INSTANCE;
int n = toIndex - fromIndex, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
TimSort.sort(a, fromIndex, toIndex, cmp, null, 0, 0);
else
new ArraysParallelSortHelpers.FJObject.Sorter<T>
(null, a,
(T[])Array.newInstance(a.getClass().getComponentType(), n),
fromIndex, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g, cmp).invoke();
}
可以看出小於1 << 13,即8192,還是原來的做法,多了纔會並行排序。
另外Collections還可以sortedMap