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
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章