Java中的Comparable接口和Comparator接口

最近Algorithms 4 課上提到了排序。趁着這個機會,梳理一下。java

1. 介紹

Comparable<T>接口和Comparator<T>接口都是JDK中提供的和比較相關的接口。使用它們能夠對對象進行比較大小,排序等操做。這算是以後排序的先導知識吧。
Comparable, 字面意思是「能夠比較的」,因此實現它的類的多個實例應該能夠相互比較「大小」或者「高低」等等。
Comparator, 字面意思是「比較儀,比較器」, 它應該是專門用來比較用的「工具」。面試

2. Comparable

Comparable<T>接口數組

public interface Comparable<T> {

    public int compareTo(T o);
}

首先看看JDK中怎麼說的:架構

This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class's <i>natural ordering</i>, and the class's <tt>compareTo</tt> method is referred to as its <i>natural comparison method</i>.<p>框架

大意是: 任何實現這個接口的類,其多個實例能以固定的次序進行排列。次序具體由接口中的方法compareTo方法決定。分佈式

Lists (and arrays) of objects that implement this interface can be sorted automatically by {@link Collections#sort(List) Collections.sort} (and {@link Arrays#sort(Object[]) Arrays.sort}).工具

若是某個類實現了這個接口,則它的List或數組都能使用Collections.sort()Arrays.sort()進行排序。
常見的類如Integer, Double, String都實現了此類。一下子會結合源碼進行分析。源碼分析

咱們先來看Integer中的實現:性能

public final class Integer extends Number implements Comparable<Integer> {

    private final int value;
    
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
    public static int compareUnsigned(int x, int y) {
        return compare(x + MIN_VALUE, y + MIN_VALUE);
    }

咱們只貼出了和比較相關的方法。
能夠看到,compareTo方法其中調用了compare方法,這是JDK1.7增長的方法。在Integer中新增這個方法是爲了減小沒必要要的自動裝箱拆箱。傳入compare方法的是兩個Integer的值xy
若是x < y, 返回-1;若是x = y, 返回0;若是x > y, 返回1
順便一說,JDK中的實現很是簡潔,只有一行代碼, 當判斷狀況有三種時,使用這種嵌套的判斷 x ? a : b 能夠簡潔很多,這是該學習的。學習

後面的compareUnsigned是JDK1.8新加入的方法, 用來比較無符號數。這裏的無符號數意思是默認二進制最高位再也不做爲符號位,而是計入數的大小。
其實現是

public static int compareUnsigned(int x, int y) {
        return compare(x + MIN_VALUE, y + MIN_VALUE);
    }

直接爲每一個值加了Integer的最小值 -231。咱們知道Java中int類型爲4個字節,共32位。符號位佔用一位的話,則其範圍爲-231 到231 - 1。
使用此方法時,全部正數都比負數小。最大值爲 -1,由於 -1的二進制全部位均爲 1。
也就是1111 1111 1111 1111 1111 1111 1111 1111 > 其它任何32位數。

具體是什麼狀況呢?

2.1 計算機編碼

首先咱們知道,在計算機中,全部數都是以二進制存在,也就是01的組合。
爲了使數字在計算機中運算不出錯,出現了原碼,反碼和補碼。原碼就是一個數的二進制表示,其中最高位爲符號位,表示其正負。
正數的原碼反碼補碼都同樣,負數的反碼是除符號位之外所有取反,補碼爲反碼加1,如圖所示爲32位bits(也就是4比特bytes)數的原碼反碼和補碼。

原碼反碼補碼.jpg

 

爲何要使用反碼和補碼呢?用四位二進制數舉例:
1的二進制爲0001-1的二進制爲1001,若是直接相加,則1 + (-1) = 0,二進制表示爲0001 + 1001 = 1010 != 0,因此不能直接使用原碼作運算。

後來出現了反碼,除符號位以外其餘位取反,1001(-1)取反後爲1110, 如今作加法 0001 (1) + 1110 (-1) = 1111 。因爲1111是負數,因此取反以後纔是其真實值,取反後爲1000,也就是-0。這能知足條件了,可是美中不足的是,0帶了負號。惟一的問題其實就出如今0這個特殊的數值上。 雖然人們理解上+0-0是同樣的, 可是0帶符號是沒有任何意義的。 並且會有0000原和1000原兩個編碼表示0。怎麼辦呢?

人們又想出了補碼,它是反碼加1-1的補碼是 1111,以上的運算用補碼錶示就是0001 (1) + 1111 (-1) = 0000 = 0。神奇的發現,這個式子完美契合了十進制加法!
同時咱們留出了1000,能夠用它表示-8

(-1) + (-7) = (補碼) 1111 + 1001 = 1000 = -8。注意,因爲此處的-8使用了以前-0的補碼來表示,因此-8沒有沒有原碼和反碼錶示(針對的四位,若是是八位,則沒有原碼和反碼的是-128,依次類推)。

使用補碼, 不只僅修復了0的符號以及存在兩個編碼的問題, 並且還可以多表示一個最低數. 這就是爲何4位二進制, 使用原碼或反碼錶示的範圍爲[-7, +7], 而使用補碼錶示的範圍爲[-8, 7].

二進制加法.jpg

這就是簡單的要用反碼和補碼的緣由。

2.2 大數溢出問題

int類型在32位系統中佔4個字節、32bit,補碼錶示的的數據範圍爲:

[10000000 00000000 00000000 00000000] ~ [01111111 11111111 11111111 11111111]

[−231,231−1]
[-2147483648, 2147483647]

在java中表示爲:

[Integer.MIN_VALUE, Integer.MAX_VALUE]

byte類型的表示同樣,因爲負數比正數多表示了一個數字。對下限去相反數後的數值會超過上限值,溢出到下限,所以下限的相反數與下限相等;對上限去相反數的數值爲負值,該負值比下限的負值大1,在能夠表示的範圍內,所以上限的相反數是上限直接取負值。

2.3 String類型的compareTo方法

看完Integer後,咱們再來看StringcompareTo的實現方式:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];     // String的值
  
    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);     // limit, 表示兩個String中長度較小的String長度
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;     // 若是char不相同,則取其差值
            }
            k++;    // 若是char值相同,則繼續日後比較
        }
        return len1 - len2;     // 若是全部0 ~ (lim - 1)的char均相同,則比較兩個String的長短
    }
  // 字面意思是對大小寫不敏感的比較器
    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {  
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);     // 和上面相似,均是取兩個String間的最短長度
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1); // 統一換成大寫
                    c2 = Character.toUpperCase(c2); // 統一換成大寫
                    if (c1 != c2) {     // 大寫若是不相等則再換爲小寫試試
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {     // 到此處則肯定不相等
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }
    // String的方法,能夠直接使用這個方法和其它String進行比較,
    // 內部實現是調用內部比較器的compare方法
    public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }   
}

String中的關於compare的方法相對複雜一點,但仍是比較簡單。咱們先不看其餘的代碼,只重點關注compareTo方法。

public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);     // limit, 表示兩個String中長度較小的String長度
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;     // 若是char不相同,則取其差值
            }
            k++;    // 若是char值相同,則繼續日後比較
        }
        return len1 - len2;     // 若是全部0 ~ (lim - 1)的char均相同,則比較兩個String的長短
    }

內容很簡潔,就是取兩個String的長度中較小的,做爲限定值(lim)。以後對數組下標爲從0lim - 1char變量進行遍歷比較,若是遇到不相同的值,返回其差值。通常咱們只用其正負性,若是返回負數則說明第一個對象比第二個對象「小」。
例如比較 "abc""bcd",當對各自第一個字符'a''b'進行比較時,發現 'a' != 'b',則返回 'a' - 'b' ,這個值是負數, char類型的-1,Java會自動將其類型強轉爲int型。最後得出結論"abc""bcd"小。

3. Comparator

Comparator<T>接口

public interface Comparator<T> {
    int compare(T o1, T o2);
}

這是一個外部排序接口,它的功能是規定「比較大小」的方式。實現它的類能夠做爲參數傳入Collections.sort()Arrays.sort(),使用它的比較方式進行排序。
它能夠爲沒有實現Comparable接口的類提供排序方式。
String類中以及Array類等都有實現此接口的內部類。

在上面String的源碼中就有一個內部的自定義ComparatorCaseInsensitiveComparator, 咱們看看它的源碼。

public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {  
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);     // 和上面相似,均是取兩個String間的最短長度
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1); // 統一換成大寫
                    c2 = Character.toUpperCase(c2); // 統一換成大寫
                    if (c1 != c2) {     // 大寫若是不相等則再換爲小寫試試
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {     // 到此處則肯定不相等
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }
    // String的方法,能夠直接使用這個方法和其它String進行比較,
    // 內部實現是調用內部比較器的compare方法
    public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }   
}

CaseInsensitiveComparator, 字面意思是對大小寫不敏感的比較器。
咱們觀察它的compare方法,能夠發現,它和上面的compareTo方法實現相似,都是取兩個String中長度較小的,做爲限定值min,以後對數組下標爲從0min - 1char變量進行遍歷比較。和上面稍有不一樣的是,此處先將char字符統一換成大寫(upper case), 若是仍然不相等,再將其換爲小寫(lower case)比較。一個字母只有大寫或者小寫兩種情形,若是這兩種狀況都不想等則肯定不相等,返回其差值。若是限定值內全部的char都相等的話,再去比較兩個String類型的長度。

例如比較 "abC""ABc"compareTo會直接返回 'a' - 'A',而compareToIgnoreCase方法因爲使用了CaseInsensitiveComparator,比較結果最終會返回true

感興趣能夠加Java架構師羣獲取Java工程化、高性能及分佈式、高性能、深刻淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的直播免費學習權限 都是大牛帶飛 讓你少走不少的彎路的 羣..號是:855801563 對了 小白勿進 最好是有開發經驗

注:加羣要求

一、具備工做經驗的,面對目前流行的技術不知從何下手,須要突破技術瓶頸的能夠加。

二、在公司待久了,過得很安逸,但跳槽時面試碰壁。須要在短期內進修、跳槽拿高薪的能夠加。

三、若是沒有工做經驗,但基礎很是紮實,對java工做機制,經常使用設計思想,經常使用java開發框架掌握熟練的,能夠加。

四、以爲本身很牛B,通常需求都能搞定。可是所學的知識點沒有系統化,很難在技術領域繼續突破的能夠加。

5.阿里Java高級大牛直播講解知識點,分享知識,多年工做經驗的梳理和總結,帶着你們全面、科學地創建本身的技術體系和技術認知!

相關文章
相關標籤/搜索