TreeMap 的排序沖突嗎


今天在網上看到一個問題:一個已經構建好的 TreeSet,怎么完成倒排序?

網上給出的答案是:

通過TreeSet構造函數傳入一個比較器,指定比較器進行排序為原排序的倒敘。
TreeSet的自然排序是根據集合元素的大小,TreeSet將他們以升序排列。如果需要實現定制排序,例如降序,則可以使用Comparator接口。該接口里包含一個int compare(T o1,T o2)方法,該方法用於比較o1和o2的大小。
如果需要實現定制排序,則需要在創建TreeSet集合對象時,並提供一個Comparator對象與該TreeSet集合關聯,由該Comparator對象負責集合元素的排序邏輯。

我們知道,如果要實現TreeSet 的 排序(或者說讓一個TreeSet可用),必須讓加入的對象具有可排序性,否則就會報錯 java.lang.ClassCastException。

實現思路有兩個(二選一即可):

1、加入的對象(相對於TreeMap,就是key對象,TreeSet 是通過 TreeMap 來實現的)需要實現 Comparable 接口

package com.cd.demo;

import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest1 {

    public static void main(String[] args) {
        // 構造一個可用的TreeSet對象:傳入對象實現 Comparable 接口 (按照年齡排序)
        Set<User> userSet = new TreeSet<>();
        userSet.add(new User(5, 32, "阿布1"));
        userSet.add(new User(12, 25, "阿布2"));
        userSet.add(new User(9, 36, "阿布3"));
        userSet.add(new User(20, 34, "阿布4"));
        userSet.add(new User(15, 48, "阿布5"));
        for (User u : userSet) {
            System.out.println(u.toString());
        }
    }

    // static 內部類,可以在main方法中 直接使用new User(...)初始化,否則就要:new TreeSetTest().new User(...) 才行
    static class User implements Comparable<User> {
        Integer id;

        Integer age;

        String name;

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

        public Integer getId() {
            return id;
        }

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

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

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

        @Override
        public int compareTo(User arg0) {
            return this.age.compareTo(arg0.getAge());
        }
    }

}
User [id=12, age=25, name=阿布2]
User [id=5, age=32, name=阿布1]
User [id=20, age=34, name=阿布4]
User [id=9, age=36, name=阿布3]
User [id=15, age=48, name=阿布5]

2、TreeSet初始化時需要一個 Comparator 接口實例 的入參

package com.cd.demo;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest2 {

    public static void main(String[] args) {
        // 構造一個可用的TreeSet對象:傳入一個Comparator 接口實例作為入參 (按照id排序)
        Set<User> userSet = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User arg0, User arg1) {
                return arg0.getId().compareTo(arg1.getId());
            }
        });
        // 因為 userSet 已經有入參 Comparator,故不會報錯
        userSet.add(new User(5, 32, "阿布1"));
        userSet.add(new User(12, 25, "阿布2"));
        userSet.add(new User(9, 36, "阿布3"));
        userSet.add(new User(20, 34, "阿布4"));
        userSet.add(new User(15, 48, "阿布5"));
        for (User u : userSet) {
            System.out.println(u.toString());
        }
    }

    static class User {
        Integer id;

        Integer age;

        String name;

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

        public Integer getId() {
            return id;
        }

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

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

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

    }

}
User [id=5, age=32, name=阿布1]
User [id=9, age=36, name=阿布3]
User [id=12, age=25, name=阿布2]
User [id=15, age=48, name=阿布5]
User [id=20, age=34, name=阿布4]

 那么,現在問題來了,如果TreeSet中 保存的對象實現了 Comparable 接口,而 TreeSet 又傳入了外部的 Comparator,會怎么樣呢,會出現排序沖突嗎?

看代碼:

package com.cd.demo;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest {

    public static void main(String[] args) {
        // 構造一個可用的TreeSet對象:要么傳入一個Comparator 接口實例作為入參,要么 key 必須實現 Comparable 接口
        // 現在同時兩個都滿足,User內部按照 age 排序,外部 Comparator 按照id 排序,會怎么樣呢
        Set<User> userSet = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User arg0, User arg1) {
                return arg0.getId().compareTo(arg1.getId());
            }
        });
        // 因為 userSet 已經有入參 Comparator,故不會報錯
        userSet.add(new User(5, 32, "阿布1"));
        userSet.add(new User(12, 25, "阿布2"));
        userSet.add(new User(9, 36, "阿布3"));
        userSet.add(new User(20, 34, "阿布4"));
        userSet.add(new User(15, 48, "阿布5"));
        // userSet 構造測試(同時實現內外排序時,按照 Comparator 外部排序來) 
        for (User u : userSet) {
            System.out.println(u.toString());
        }
    }

    // static 內部類,可以在main方法中 直接使用new User(...)初始化,否則就要:new TreeSetTest().new User(...) 才行
    static class User implements Comparable<User> {
        Integer id;

        Integer age;

        String name;

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

        public Integer getId() {
            return id;
        }

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

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

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

        @Override
        public int compareTo(User arg0) {
            return this.age.compareTo(arg0.getAge());
        }
    }

}
User [id=5, age=32, name=阿布1]
User [id=9, age=36, name=阿布3]
User [id=12, age=25, name=阿布2]
User [id=15, age=48, name=阿布5]
User [id=20, age=34, name=阿布4]

  可以看出,如果同時實現了內外排序(對象實現 Comparable 接口、TreeSet 初始化 傳入 Comparator 接口對象),則排序 以 Comparator 的外部排序進行

看一下TreeSet 的 add 方法源碼,內部其實調用的是TreeMap 的 put:

public V put(K key, V value) {
  // 根節點
  Entry<K,V> t = root;
  // 如果根節點為空,則直接創建一個根節點,返回
  if (t == null) {
     // TBD:
     // 5045147: (coll) Adding null to an empty TreeSet should
     // throw NullPointerException
     //
     // compare(key, key); // type check
      root = new Entry<K,V>(key, value, null);
      size = 1;
      modCount++;
      return null;
  }
  // 記錄比較結果
  int cmp;
  Entry<K,V> parent;
  // split comparator and comparable paths
  // 當前使用的比較器
  Comparator<? super K> cpr = comparator ;
  // 如果比較器不為空,就是用指定的比較器來維護TreeMap的元素順序
  if (cpr != null) {
       // do while循環,查找key要插入的位置(也就是新節點的父節點是誰)
      do {
          // 記錄上次循環的節點t
          parent = t;
          // 比較當前節點的key和新插入的key的大小
          cmp = cpr.compare(key, t. key);
           // 新插入的key小的話,則以當前節點的左孩子節點為新的比較節點
          if (cmp < 0)
              t = t. left;
          // 新插入的key大的話,則以當前節點的右孩子節點為新的比較節點
          else if (cmp > 0)
              t = t. right;
          else
        // 如果當前節點的key和新插入的key想的的話,則覆蓋map的value,返回
              return t.setValue(value);
      // 只有當t為null,也就是沒有要比較節點的時候,代表已經找到新節點要插入的位置
      } while (t != null);
  }
  else {
      // 如果比較器為空,則使用key作為比較器進行比較
      // 這里要求key不能為空,並且必須實現Comparable接口
      if (key == null)
          throw new NullPointerException();
      Comparable<? super K> k = (Comparable<? super K>) key;
      // 和上面一樣,喜歡查找新節點要插入的位置
      do {
          parent = t;
          cmp = k.compareTo(t. key);
          if (cmp < 0)
              t = t. left;
          else if (cmp > 0)
              t = t. right;
          else
              return t.setValue(value);
      } while (t != null);
  }
  // 找到新節點的父節點后,創建節點對象
  Entry<K,V> e = new Entry<K,V>(key, value, parent);
  // 如果新節點key的值小於父節點key的值,則插在父節點的左側
  if (cmp < 0)
      parent. left = e;
  // 如果新節點key的值大於父節點key的值,則插在父節點的右側
  else
      parent. right = e;
  // 插入新的節點后,為了保持紅黑樹平衡,對紅黑樹進行調整
  fixAfterInsertion(e);
  // map元素個數+1
  size++;
  modCount++;
  return null;
}

注意看上面的下划線部分代碼,jdk 中 默認是先使用 Comparator ,如果沒有 Comparator ,才使用對象的 Comparable 接口。

那回到開篇的問題,如果需要實現倒序,就得分兩種情況了:

1、只實現了 Comparable 的倒序;

2、實現了 Comparator 的倒序。

這兩種情況,都可以通過 重新 建立一個 TreeSet,傳入倒序的 Comparator 來實現倒序,偽代碼為:

        Set<User> userSetNew = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User arg0, User arg1) {
                return ...; // 新的排序邏輯
            }
        });
        userSetNew.addAll(userSetOld);
        System.out.println("---------------  倒序后  ----------------");
        for (User u : userSetNew ) {
            System.out.println(u.toString());
        }

這個思路有一種投機取巧的方法,就是 compare 方法中默認返回 -1,比如上面的代碼實現倒序可以為:

package com.cd.demo;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest {

    public static void main(String[] args) {
        // 構造一個可用的TreeSet對象:要么傳入一個Comparator 接口實例作為入參,要么 key 必須實現 Comparable 接口
        // 現在同時兩個都滿足,User內部按照 age 排序,外部 Comparator 按照id 排序,會怎么樣呢
        Set<User> userSet = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User arg0, User arg1) {
                return arg0.getId().compareTo(arg1.getId());
            }
        });
        // 因為 userSet 已經有入參 Comparator,故不會報錯
        userSet.add(new User(5, 32, "阿布1"));
        userSet.add(new User(12, 25, "阿布2"));
        userSet.add(new User(9, 36, "阿布3"));
        userSet.add(new User(20, 34, "阿布4"));
        userSet.add(new User(15, 48, "阿布5"));
        // userSet 構造測試(同時實現內外排序時,按照 Comparator 外部排序來) 
        for (User u : userSet) {
            System.out.println(u.toString());
        }

        Set<User> userSet2 = new TreeSet<>(new Comparator<User>() {
            @Override
            public int compare(User arg0, User arg1) {
                return -1;
            }
        });
        userSet2.addAll(userSet);
        System.out.println("---------------  倒序后  ----------------");
        for (User u : userSet2) {
            System.out.println(u.toString());
        }
    }

    // static 內部類,可以在main方法中 直接使用new User(...)初始化,否則就要:new TreeSetTest().new User(...) 才行
    static class User implements Comparable<User> {
        Integer id;

        Integer age;

        String name;

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

        public Integer getId() {
            return id;
        }

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

        public Integer getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

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

        @Override
        public int compareTo(User arg0) {
            return this.age.compareTo(arg0.getAge());
        }
    }

}
User [id=5, age=32, name=阿布1]
User [id=9, age=36, name=阿布3]
User [id=12, age=25, name=阿布2]
User [id=15, age=48, name=阿布5]
User [id=20, age=34, name=阿布4]
---------------  倒序后  ----------------
User [id=20, age=34, name=阿布4]
User [id=15, age=48, name=阿布5]
User [id=12, age=25, name=阿布2]
User [id=9, age=36, name=阿布3]
User [id=5, age=32, name=阿布1]

  


免責聲明!

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



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