Java中的Comparable接口和Comparator接口

top.jpg

最近Algorithms 4 課上提到了排序酪惭。趁著這個(gè)機(jī)會(huì)兄世,梳理一下伤哺。

1. 介紹

Comparable<T>接口和Comparator<T>接口都是JDK中提供的和比較相關(guān)的接口纸巷。使用它們可以對(duì)對(duì)象進(jìn)行比較大小贞绵,排序等操作厉萝。這算是之后排序的先導(dǎo)知識(shí)吧。
Comparable榨崩, 字面意思是“可以比較的”谴垫,所以實(shí)現(xiàn)它的類的多個(gè)實(shí)例應(yīng)該可以相互比較“大小”或者“高低”等等。
Comparator蜡饵, 字面意思是“比較儀弹渔,比較器”, 它應(yīng)該是專門用來(lái)比較用的“工具”溯祸。

2. Comparable

Comparable<T>接口

public interface Comparable<T> {

    public int compareTo(T o);
}

首先看看JDK中怎么說(shuō)的:

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>

大意是: 任何實(shí)現(xiàn)這個(gè)接口的類肢专,其多個(gè)實(shí)例能以固定的次序進(jìn)行排列舞肆。次序具體由接口中的方法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}).

如果某個(gè)類實(shí)現(xiàn)了這個(gè)接口博杖,則它的List或數(shù)組都能使用Collections.sort()Arrays.sort()進(jìn)行排序椿胯。
常見(jiàn)的類如Integer, Double, String都實(shí)現(xiàn)了此類。一會(huì)兒會(huì)結(jié)合源碼進(jìn)行分析剃根。

我們先來(lái)看Integer中的實(shí)現(xiàn):

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);
    }

我們只貼出了和比較相關(guān)的方法哩盲。
可以看到,compareTo方法其中調(diào)用了compare方法狈醉,這是JDK1.7增加的方法廉油。在Integer中新增這個(gè)方法是為了減少不必要的自動(dòng)裝箱拆箱。傳入compare方法的是兩個(gè)Integer的值xy苗傅。
如果x < y抒线, 返回-1;如果x = y渣慕, 返回0嘶炭;如果x > y, 返回1逊桦。
順便一說(shuō)眨猎,JDK中的實(shí)現(xiàn)非常簡(jiǎn)潔,只有一行代碼强经, 當(dāng)判斷情況有三種時(shí)睡陪,使用這種嵌套的判斷 x ? a : b 可以簡(jiǎn)潔不少,這是該學(xué)習(xí)的匿情。

后面的compareUnsigned是JDK1.8新加入的方法, 用來(lái)比較無(wú)符號(hào)數(shù)宝穗。這里的無(wú)符號(hào)數(shù)意思是默認(rèn)二進(jìn)制最高位不再作為符號(hào)位,而是計(jì)入數(shù)的大小码秉。
其實(shí)現(xiàn)是

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

直接為每個(gè)值加了Integer的最小值 -231。我們知道Java中int類型為4個(gè)字節(jié)鸡号,共32位转砖。符號(hào)位占用一位的話,則其范圍為-231 到231 - 1鲸伴。
使用此方法時(shí)府蔗,所有正數(shù)都比負(fù)數(shù)小。最大值為 -1汞窗,因?yàn)?-1的二進(jìn)制所有位均為 1姓赤。
也就是1111 1111 1111 1111 1111 1111 1111 1111 > 其它任何32位數(shù)。

具體是什么情況呢仲吏?

2.1 計(jì)算機(jī)編碼

首先我們知道不铆,在計(jì)算機(jī)中蝌焚,所有數(shù)都是以二進(jìn)制存在,也就是01的組合誓斥。
為了使數(shù)字在計(jì)算機(jī)中運(yùn)算不出錯(cuò)只洒,出現(xiàn)了原碼,反碼和補(bǔ)碼劳坑。原碼就是一個(gè)數(shù)的二進(jìn)制表示毕谴,其中最高位為符號(hào)位,表示其正負(fù)距芬。
正數(shù)的原碼反碼補(bǔ)碼都一樣涝开,負(fù)數(shù)的反碼是除符號(hào)位以外全部取反,補(bǔ)碼為反碼加1框仔,如圖所示為32位bits(也就是4比特bytes)數(shù)的原碼反碼和補(bǔ)碼舀武。

原碼反碼補(bǔ)碼.jpg

為什么要使用反碼和補(bǔ)碼呢?用四位二進(jìn)制數(shù)舉例:
1的二進(jìn)制為0001存和,-1的二進(jìn)制為1001奕剃,如果直接相加,則1 + (-1) = 0捐腿,二進(jìn)制表示為0001 + 1001 = 1010 != 0纵朋,所以不能直接使用原碼做運(yùn)算。

后來(lái)出現(xiàn)了反碼茄袖,除符號(hào)位之外其他位取反操软,1001(-1)取反后為1110, 現(xiàn)在做加法 0001 (1) + 1110 (-1) = 1111 宪祥。由于1111是負(fù)數(shù)聂薪,所以取反之后才是其真實(shí)值,取反后為1000蝗羊,也就是-0藏澳。這能滿足條件了,但是美中不足的是耀找,0帶了負(fù)號(hào)翔悠。唯一的問(wèn)題其實(shí)就出現(xiàn)在0這個(gè)特殊的數(shù)值上。 雖然人們理解上+0-0是一樣的野芒, 但是0帶符號(hào)是沒(méi)有任何意義的蓄愁。 而且會(huì)有0000原和1000原兩個(gè)編碼表示0。怎么辦呢狞悲?

人們又想出了補(bǔ)碼撮抓,它是反碼加1-1的補(bǔ)碼是 1111摇锋,以上的運(yùn)算用補(bǔ)碼表示就是0001 (1) + 1111 (-1) = 0000 = 0丹拯。神奇的發(fā)現(xiàn)站超,這個(gè)式子完美契合了十進(jìn)制加法!
同時(shí)我們留出了1000咽笼,可以用它表示-8

(-1) + (-7) = (補(bǔ)碼) 1111 + 1001 = 1000 = -8顷编。注意,由于此處的-8使用了之前-0的補(bǔ)碼來(lái)表示剑刑,所以-8沒(méi)有沒(méi)有原碼和反碼表示(針對(duì)的四位媳纬,如果是八位,則沒(méi)有原碼和反碼的是-128施掏,依次類推)钮惠。

使用補(bǔ)碼, 不僅僅修復(fù)了0的符號(hào)以及存在兩個(gè)編碼的問(wèn)題, 而且還能夠多表示一個(gè)最低數(shù). 這就是為什么4位二進(jìn)制, 使用原碼或反碼表示的范圍為[-7, +7], 而使用補(bǔ)碼表示的范圍為[-8, 7].

二進(jìn)制加法.jpg

這就是簡(jiǎn)單的要用反碼和補(bǔ)碼的原因。

2.2 大數(shù)溢出問(wèn)題

int類型在32位系統(tǒng)中占4個(gè)字節(jié)七芭、32bit素挽,補(bǔ)碼表示的的數(shù)據(jù)范圍為:

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

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

在java中表示為:

[Integer.MIN_VALUE, Integer.MAX_VALUE]

byte類型的表示一樣,由于負(fù)數(shù)比正數(shù)多表示了一個(gè)數(shù)字狸驳。對(duì)下限去相反數(shù)后的數(shù)值會(huì)超過(guò)上限值预明,溢出到下限,因此下限的相反數(shù)與下限相等耙箍;對(duì)上限去相反數(shù)的數(shù)值為負(fù)值撰糠,該負(fù)值比下限的負(fù)值大1,在可以表示的范圍內(nèi)辩昆,因此上限的相反數(shù)是上限直接取負(fù)值阅酪。

2.3 String類型的compareTo方法

看完Integer后,我們?cè)賮?lái)看StringcompareTo的實(shí)現(xiàn)方式:

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汁针, 表示兩個(gè)String中長(zhǎng)度較小的String長(zhǎng)度
        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值相同,則繼續(xù)往后比較
        }
        return len1 - len2;     // 如果所有0 ~ (lim - 1)的char均相同施无,則比較兩個(gè)String的長(zhǎng)短
    }
  // 字面意思是對(duì)大小寫不敏感的比較器
    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);     // 和上面類似辉词,均是取兩個(gè)String間的最短長(zhǎng)度
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1); // 統(tǒng)一換成大寫
                    c2 = Character.toUpperCase(c2); // 統(tǒng)一換成大寫
                    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的方法,可以直接使用這個(gè)方法和其它String進(jìn)行比較猾骡,
    // 內(nèi)部實(shí)現(xiàn)是調(diào)用內(nèi)部比較器的compare方法
    public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }   
}

String中的關(guān)于compare的方法相對(duì)復(fù)雜一點(diǎn)较屿,但還是比較簡(jiǎn)單。我們先不看其他的代碼卓练,只重點(diǎn)關(guān)注compareTo方法。

    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);     // limit购啄, 表示兩個(gè)String中長(zhǎng)度較小的String長(zhǎng)度
        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值相同,則繼續(xù)往后比較
        }
        return len1 - len2;     // 如果所有0 ~ (lim - 1)的char均相同狮含,則比較兩個(gè)String的長(zhǎng)短
    }

內(nèi)容很簡(jiǎn)潔顽悼,就是取兩個(gè)String的長(zhǎng)度中較小的曼振,作為限定值(lim)。之后對(duì)數(shù)組下標(biāo)為從0lim - 1char變量進(jìn)行遍歷比較蔚龙,如果遇到不相同的值冰评,返回其差值。一般我們只用其正負(fù)性木羹,如果返回負(fù)數(shù)則說(shuō)明第一個(gè)對(duì)象比第二個(gè)對(duì)象“小”甲雅。
例如比較 "abc""bcd",當(dāng)對(duì)各自第一個(gè)字符'a''b'進(jìn)行比較時(shí)坑填,發(fā)現(xiàn) 'a' != 'b'抛人,則返回 'a' - 'b' ,這個(gè)值是負(fù)數(shù)脐瑰, char類型的-1妖枚,Java會(huì)自動(dòng)將其類型強(qiáng)轉(zhuǎn)為int型。最后得出結(jié)論"abc""bcd"小苍在。

3. Comparator

Comparator<T>接口

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

這是一個(gè)外部排序接口绝页,它的功能是規(guī)定“比較大小”的方式。實(shí)現(xiàn)它的類可以作為參數(shù)傳入Collections.sort()Arrays.sort()寂恬,使用它的比較方式進(jìn)行排序续誉。
它可以為沒(méi)有實(shí)現(xiàn)Comparable接口的類提供排序方式。
String類中以及Array類等都有實(shí)現(xiàn)此接口的內(nèi)部類掠剑。

在上面String的源碼中就有一個(gè)內(nèi)部的自定義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);     // 和上面類似朴译,均是取兩個(gè)String間的最短長(zhǎng)度
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1); // 統(tǒng)一換成大寫
                    c2 = Character.toUpperCase(c2); // 統(tǒng)一換成大寫
                    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的方法井佑,可以直接使用這個(gè)方法和其它String進(jìn)行比較,
    // 內(nèi)部實(shí)現(xiàn)是調(diào)用內(nèi)部比較器的compare方法
    public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }   
}

CaseInsensitiveComparator, 字面意思是對(duì)大小寫不敏感的比較器眠寿。
我們觀察它的compare方法躬翁,可以發(fā)現(xiàn),它和上面的compareTo方法實(shí)現(xiàn)類似盯拱,都是取兩個(gè)String中長(zhǎng)度較小的盒发,作為限定值min,之后對(duì)數(shù)組下標(biāo)為從0min - 1char變量進(jìn)行遍歷比較狡逢。和上面稍有不同的是宁舰,此處先將char字符統(tǒng)一換成大寫(upper case), 如果仍然不相等奢浑,再將其換為小寫(lower case)比較蛮艰。一個(gè)字母只有大寫或者小寫兩種情形,如果這兩種情況都不想等則確定不相等雀彼,返回其差值壤蚜。如果限定值內(nèi)所有的char都相等的話即寡,再去比較兩個(gè)String類型的長(zhǎng)度。

例如比較 "abC""ABc"袜刷,compareTo會(huì)直接返回 'a' - 'A'聪富,而compareToIgnoreCase方法由于使用了CaseInsensitiveComparator,比較結(jié)果最終會(huì)返回true著蟹。

參考文章

[1] Java源碼學(xué)習(xí)之Integer類(二)——1.8新增的幾個(gè)函數(shù)和變量
[2] 計(jì)算機(jī)原碼墩蔓、反碼、補(bǔ)碼詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末草则,一起剝皮案震驚了整個(gè)濱河市钢拧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌炕横,老刑警劉巖源内,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異份殿,居然都是意外死亡膜钓,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門卿嘲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颂斜,“玉大人,你說(shuō)我怎么就攤上這事拾枣∥执” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵梅肤,是天一觀的道長(zhǎng)司蔬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)姨蝴,這世上最難降的妖魔是什么俊啼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮左医,結(jié)果婚禮上授帕,老公的妹妹穿的比我還像新娘。我一直安慰自己浮梢,他們只是感情好跛十,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著秕硝,像睡著了一般偶器。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天屏轰,我揣著相機(jī)與錄音,去河邊找鬼憋飞。 笑死霎苗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的榛做。 我是一名探鬼主播唁盏,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼检眯!你這毒婦竟也來(lái)了厘擂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锰瘸,失蹤者是張志新(化名)和其女友劉穎刽严,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體避凝,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舞萄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了管削。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒脓。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖含思,靈堂內(nèi)的尸體忽然破棺而出崎弃,到底是詐尸還是另有隱情,我是刑警寧澤含潘,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布饲做,位于F島的核電站,受9級(jí)特大地震影響调鬓,放射性物質(zhì)發(fā)生泄漏艇炎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一腾窝、第九天 我趴在偏房一處隱蔽的房頂上張望缀踪。 院中可真熱鬧,春花似錦虹脯、人聲如沸驴娃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)唇敞。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疆柔,已是汗流浹背咒精。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旷档,地道東北人模叙。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鞋屈,于是被迫代替她去往敵國(guó)和親范咨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 網(wǎng)站亂碼問(wèn)題我們會(huì)經(jīng)常碰到厂庇,大多見(jiàn)于非英文的中文字符或其他字符亂碼渠啊,而且,這類問(wèn)題常常是因?yàn)榫幋a方式問(wèn)題权旷,主要原因...
    波段頂?shù)?/span>閱讀 2,821評(píng)論 1 9
  • Java源碼 Integer Integer的簽名如下替蛉,繼承了Number類并實(shí)現(xiàn)Comparable接口 Com...
    wngn123閱讀 1,244評(píng)論 0 2
  • 書(shū)中關(guān)于原碼坤邪、反碼熙含、補(bǔ)碼和移碼的定義如下(n是機(jī)器字長(zhǎng)):原碼: 反碼: 補(bǔ)碼: 移碼: 原碼, 反碼, 補(bǔ)碼的基...
    困卡閱讀 15,841評(píng)論 2 8
  • As i was sitting there, i suddenly noticed a little girl....
    三木之墓閱讀 207評(píng)論 0 0
  • 一 “成亮,還不走嗎艇纺?”剛剛出門...
    游邁閱讀 298評(píng)論 0 1