Java中的TreeMap、Comparable、Comparator


  我們知道HashMap的存儲位置是按照key這個對象的hashCode來存放的,而TreeMap則是不是按照hashCode來存放,他是按照實現的Comparable接口的compareTo這個方法來存儲的,只要compareTo的返回結果為0就表示兩個對象相等,那么就存不進去兩個對象,后put的就把前面的覆蓋掉,甚至我們都不用重寫equasls和hashCode方法,而只需要實現Comparable接口來重寫comparareTo方法就行了,但是我們不能保證在應用中不會用到HashMap,所以保持良好的習慣,當我們定義了一個對象之后習慣性的重寫equals和hashCode方法。本文比較詳細的解釋了TreeMap、Comparable、Comparator這三者的關聯。

  測試Comparable接口:

  第一次比較:定義一個User類,實現Comparable接口,按照年齡排序,我們讓equals為true,而hashCode也始終相等。

  

public class User implements Comparable<User> {
    private String id;
    private String name;
    private Integer age;

    public User() {
    }

    public User(String id, String name, Integer age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }

    @Override
    public boolean equals(Object obj) {
        return true;
    }
    
    @Override
    public int hashCode() {
        return 0;
    }
    public int compareTo(User o) {
        return this.age > o.getAge() ? 1 : this.age == o.getAge() ? 0 : -1;
    }
}

  測試代碼:

public class TestUser {
    public static void main(String[] args) {
        Map<User, Integer> userHashMap = new HashMap<User, Integer>();
        User user1 = new User("1", "Jay", 30);
        User user2 = new User("2", "Jolin", 21);
        User user3 = new User("3", "Jack Cheng", 22);
        User user4 = new User("4", "Bruce Lee", 22);
        userHashMap.put(user1, 100);
        userHashMap.put(user2, 200);
        userHashMap.put(user3, 300);
        userHashMap.put(user4, 400);
        
        System.out.println(userHashMap);
        
        Map<User, Integer>  userTreeMap = new TreeMap<User, Integer>();
        userTreeMap.put(user1, 100);
        userTreeMap.put(user2, 200);
        userTreeMap.put(user3, 300);
        userTreeMap.put(user4, 400);
        
        System.out.println(userTreeMap);
    }
}

  結果:

{User [name=Jay, age=30]=400}
{User [name=Jolin, age=21]=200, User [name=Jack Cheng, age=22]=400, User [name=Jay, age=30]=100}

  結論:對於HashMap而言,只要key的equals相等就表示兩個元素相等,HashMap就存不進去;而TreeMap是不管equals和hashCode的,只要compareTo相等就表示兩個元素相同,就存不進去。

  2.第二次比較:現在我們按照id來重寫hashCode和equals方法,如下:

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

測試代碼不變,還是上面的測試代碼,結果:

{User [name=Jolin, age=21]=200, User [name=Jay, age=30]=100, User [name=Bruce Lee, age=22]=400, User [name=Jack Cheng, age=22]=300}
{User [name=Jolin, age=21]=200, User [name=Jack Cheng, age=22]=400, User [name=Jay, age=30]=100}

說明:HashMap只要equals不等那就表示不等,而對於TreeMap如果compareTo相等,那么2個元素就相等,並且排序是按照compareTo方法定義的排序規則。

 

接下來再在測試代碼里面添加List測試:

        List<User> userList = new ArrayList<User>();
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);
        userList.add(user4);
        
        System.out.println(userList);
        
        Collections.sort(userList);
        System.out.println(userList);

結果:

{User [name=Jolin, age=21]=200, User [name=Jay, age=30]=100, User [name=Bruce Lee, age=22]=400, User [name=Jack Cheng, age=22]=300}
{User [name=Jolin, age=21]=200, User [name=Jack Cheng, age=22]=400, User [name=Jay, age=30]=100}
[User [name=Jay, age=30], User [name=Jolin, age=21], User [name=Jack Cheng, age=22], User [name=Bruce Lee, age=22]]
[User [name=Jolin, age=21], User [name=Jack Cheng, age=22], User [name=Bruce Lee, age=22], User [name=Jay, age=30]]

當調用sort方法之后List里面的元素就按照age排序了。

對於Comparator,一種叫做策略模式的設計模式,我們在User中實現Comparable接口,重寫里面的方法,可以實現對User按age排序,這個排序算法是存在於User內部的,換句話說User和排序算法是相互依存的,User只能用compareTo這個算法,而compareTo算法也只能為User服務,那么如果我們其他地方也有同樣的需求,就只能再實現一次Comparable,來為另外一個對象服務,這就有點重復的感覺,而且代碼沒有得到復用,樣子對象和算法混在一起耦合性很強,於是希望把算法和模型分離出來,讓算法單獨存在,不同的對象可以使用同一個算法,並且二者是分離的,沒有混為一體。當然可以分開那必然還是可以放在一起的。用一句比較專業點的話來講就是策略模式有兩個特點:1.封裝變化。2.變成中使用接口而不實現接口。如果我再說得直白一點,策略模式就是一種面向接口編程的思想。這點在多線程里面也有所體現,就是為什么我們在開啟新線程的時候要new Thread(Runnable接口),參數傳接口而不推薦去繼承Thread,雖然也可以繼承,這也是體現面向對象的封裝特性,將run的算法和線程分離,可以實現run所在類的復用,雖然一般都不會去復用,當然這里還有一點就是Java只能單繼承,如果繼承了Thread就不能在繼承其他的類。此時我們的User是按照age排序,那如果想按照名字排序呢,那就沒辦法了,但是如果將算法分離出來,需要用名字排序我們就傳用名字排序的算法進去,需要用age排序就傳age排序算法進去。環境持有接口引用,通過set方法將接口實現傳入到環境,在環境中來調用接口方法,這完完全全是面向接口的特點。下面的第一個例子就是存放在一起的,因為TreeMap的構造方法其中就有一個是待Comparator接口參數的構造方法,該參數只要實現了Comparator接口就行,如下:

public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

舉例:

有一個Person類,實現了Comparator接口,並且按照歲數排序:

public class Person implements Comparator<Person> {
    private String id;
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

    public int compare(Person o1, Person o2) {
        return o1.getAge() > o2.getAge() ? 1 : o1.getAge() == o2.getAge() ? 0 : -1;
    }
    

}

測試:

public class TestPerson {
    public static void main(String[] args) {
        Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
        Person person1 = new Person("1", "Jay", 30);
        Person person2 = new Person("2", "Jolin", 21);
        Person person3 = new Person("3", "Jack Cheng", 22);
        Person person4 = new Person("4", "Bruce Lee", 22);
        personHashMap.put(person1, 100);
        personHashMap.put(person2, 200);
        personHashMap.put(person3, 300);
        personHashMap.put(person4, 400);
        
        System.out.println(personHashMap);
        
        Map<Person, Integer>  personTreeMap = new TreeMap<Person, Integer>(new Person());
        personTreeMap.put(person1, 100);
        personTreeMap.put(person2, 200);
        personTreeMap.put(person3, 300);
        personTreeMap.put(person4, 400);
        
        System.out.println(personTreeMap);
        
    }
}

結果:

{Person [name=Jolin, age=21]=200, Person [name=Jay, age=30]=100, Person [name=Bruce Lee, age=22]=400, Person [name=Jack Cheng, age=22]=300}
{Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}

依然是compara方法相等的元素表示相同,只能存放一個進去,並且是按照age排序的。

在List中的結果:

public class TestPerson {
    public static void main(String[] args) {
        Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
        Person person1 = new Person("1", "Jay", 30);
        Person person2 = new Person("2", "Jolin", 21);
        Person person3 = new Person("3", "Jack Cheng", 22);
        Person person4 = new Person("4", "Bruce Lee", 22);
        personHashMap.put(person1, 100);
        personHashMap.put(person2, 200);
        personHashMap.put(person3, 300);
        personHashMap.put(person4, 400);
        
        System.out.println(personHashMap);
        
        Map<Person, Integer>  personTreeMap = new TreeMap<Person, Integer>(new Person());
        personTreeMap.put(person1, 100);
        personTreeMap.put(person2, 200);
        personTreeMap.put(person3, 300);
        personTreeMap.put(person4, 400);
        
        System.out.println(personTreeMap);
        
        List<Person> personList = new ArrayList<Person>();
        
        personList.add(person1);
        personList.add(person2);
        personList.add(person3);
        personList.add(person4);
        
        System.out.println(personList);
        
        Collections.sort(personList, new Person());
        
        System.out.println(personList);
        
    }
}

結果:

{Person [name=Jolin, age=21]=200, Person [name=Jay, age=30]=100, Person [name=Bruce Lee, age=22]=400, Person [name=Jack Cheng, age=22]=300}
{Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}
[Person [name=Jay, age=30], Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22]]
[Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22], Person [name=Jay, age=30]]

調用sort方法前沒排序,而調用之后按照age排序了。

其實將算法和模型分離也非常類似,只需要將實現Comparator接口的類單獨拿出去就ok了。現在定義Person類,不實現Comparator接口,而將SortPerson類來實現該接口,其實就是用SortPerson類代替之前TreeMap中的參數,之前的參數是實現了Comparator接口的Person對象。

Person類:
public
class Person { private String id; private String name; private Integer age; public Person() { } public Person(String id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } }
SortPerson類:
public class SortPerson implements Comparator<Person> {

    public int compare(Person o1, Person o2) {
        return o1.getAge() > o2.getAge() ? 1 : o1.getAge() == o2.getAge() ? 0 : -1;
    }

}

測試:

public class TestPerson {
    public static void main(String[] args) {
        Map<Person, Integer> personHashMap = new HashMap<Person, Integer>();
        Person person1 = new Person("1", "Jay", 30);
        Person person2 = new Person("2", "Jolin", 21);
        Person person3 = new Person("3", "Jack Cheng", 22);
        Person person4 = new Person("4", "Bruce Lee", 22);
        personHashMap.put(person1, 100);
        personHashMap.put(person2, 200);
        personHashMap.put(person3, 300);
        personHashMap.put(person4, 400);
        
        System.out.println(personHashMap);
        
        Map<Person, Integer>  personTreeMap = new TreeMap<Person, Integer>(new SortPerson());
        personTreeMap.put(person1, 100);
        personTreeMap.put(person2, 200);
        personTreeMap.put(person3, 300);
        personTreeMap.put(person4, 400);
        
        System.out.println(personTreeMap);
        
        List<Person> personList = new ArrayList<Person>();
        
        personList.add(person1);
        personList.add(person2);
        personList.add(person3);
        personList.add(person4);
        
        System.out.println(personList);
        
        Collections.sort(personList, new SortPerson());
        
        System.out.println(personList);
        
    }
}

結果:

{Person [name=Jolin, age=21]=200, Person [name=Jay, age=30]=100, Person [name=Bruce Lee, age=22]=400, Person [name=Jack Cheng, age=22]=300}
{Person [name=Jolin, age=21]=200, Person [name=Jack Cheng, age=22]=400, Person [name=Jay, age=30]=100}
[Person [name=Jay, age=30], Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22]]
[Person [name=Jolin, age=21], Person [name=Jack Cheng, age=22], Person [name=Bruce Lee, age=22], Person [name=Jay, age=30]]

很明顯:結果與之前的將算法實現在Person類里面的結果一致的。

寫的太多了,就不從JDK源碼的角度來看問題了,如果有興趣可以看看JDK源碼,大致思路是才用紅黑樹來存數據,通過比較器的比較結果來判斷當前需要保存的元素位於什么地方,還是那句話,既然底層用到了比較排序,那么性能肯定是比較差的,可以說是犧牲性能來做到排序功能,這個很容易理解,因為Java里面或者說計算機語言里面都是這個思路,要獲得某種特定的能力,必然是需要犧牲性能,當然有時候為了獲得良好的性能,那就必須要犧牲內存或者硬盤空間。比如HashMap為了獲得非常強大的根據key來讀取數據的性能,采用hash算法來犧牲一定的內存空間,再比如,數據庫里面為了獲得查詢性能,給某些列添加索引,犧牲硬盤存儲空間來達到效果,當然像數據庫的為了獲得查詢和統計等性能為某些列添加聚簇索引,這種對硬盤空間的犧牲那是相當的驚人。索引占用空間比表里面的數據還大,但是獲得的性能也是非常優秀的,所以,從某種意義上說,為了獲取某種特性來犧牲另外一些資源那也是值得的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM