Java解惑之TreeSet是如何去重的


引言: 最近在處理一個問題,大致是這個樣子,從數據庫里面取出一個集合,取出來的數據放到一個JavaBean里面。結果得到的集合長度為1.

TreeSetSet的一個實現,默認實現排序;故TreeSet的泛型類型必須是Comparable或者Comparator。TreeSet基於TreeMap實現。

TreeSet類圖

實例

public class Person implements Comparable<Person>{
    private String name;
    private int order;
    public Person(String name , int order){
        this.name = name;
        this.order = order;
    }
    public static void main(String [] args){
        TreeSet<Person> users = new TreeSet<Person>();
        users.add(new Person("Jason Kidd",1));
        users.add(new Person("Kobe Bryant",1));
        users.add(new Person("Jasme Harden",1));
        users.add(new Person("Leborn James",1));
        System.out.println(" size= "users.size());
    }
    // getter and setter
}
size= 1

認知被改寫

上面代碼的結果應該是4,因為我們沒有重寫hashCode和equals方法啊,Set的去重邏輯變了嗎?

重寫上面的例子:

public class Person implements Comparable<Person>{
    public static void main(String [] args){
        TreeSet<Person> users = new TreeSet<Person>();
        users.add(new Person("Jason Kidd",1));
        users.add(new Person("Kobe Bryant",2));
        users.add(new Person("Jasme Harden",1));
        users.add(new Person("Leborn James",1));
        System.out.println(" size= "users.size());
    }
    // getter and setter
}
size= 2

表面看好像是compareTo方法覺得了去重;為了證實這個猜測,進入下面的代碼里面去證實下吧。

源碼分析

理論依據

 * <p>Note that the ordering maintained by a set (whether or not an explicit
 * comparator is provided) must be <i>consistent with equals</i> if it is to
 * correctly implement the {@code Set} interface.  (See {@code Comparable}
 * or {@code Comparator} for a precise definition of <i>consistent with
 * equals</i>.)  This is so because the {@code Set} interface is defined in
 * terms of the {@code equals} operation, but a {@code TreeSet} instance
 * performs all element comparisons using its {@code compareTo} (or
 * {@code compare}) method, so two elements that are deemed equal by this method
 * are, from the standpoint of the set, equal.  The behavior of a set
 * <i>is</i> well-defined even if its ordering is inconsistent with equals; it
 * just fails to obey the general contract of the {@code Set} interface.
 //翻譯
注意,如果要正確實現 Set 接口,則 set 維護的順序(無論是否提供了顯式比較器)必須與 equals 一致。
(關於與 equals 一致 的精確定義,請參閱 Comparable 或 Comparator。)這是因為 Set 接口是按照
 equals 操作定義的,但 TreeSet 實例使用它的 compareTo(或 compare)方法對所有元素進行比較
 ,因此從 set 的觀點來看,此方法認為相等的兩個元素就是相等的。即使 set 的順序與 equals 不一致,
  其行為也是 定義良好的;它只是違背了 Set 接口的常規協定。

上面提到了TreeSet的equal方法是依據Comparable或者Comparator接口判斷的,和前面測試的結果一致。

源碼解讀:

代碼流轉:

TreeSet add

前面提過了,TreeSet是底層是基於TreeMap存儲數據的:

public class TreeSet{
  public TreeSet() {
      this(new TreeMap<E,Object>());
  }
}

構造TreeSet的時候,並沒有檢查參數類型必須是Comparable或者Comparator的實現類。

public class TreeSet{
  private static final Object PRESENT = new Object();
  public boolean add(E e) {
     return m.put(e, PRESENT)==null;
  }
}

添加元素的操作,實際就是如何給TreeMap增加元素的操作。

// java.util.TreeMap.java
public V put(K key, V value) {
  Entry<K,V> t = root;
  if (t == null) {
      compare(key, key); // type (and possibly null) check
      root = new Entry<>(key, value, null);
      size = 1;
      modCount++;
      return null;
  }
  int cmp;
  Entry<K,V> parent;
  // split comparator and comparable paths
  Comparator<? super K> cpr = comparator;
  if (cpr != null) {
      do {
          parent = t;
          cmp = cpr.compare(key, t.key);
          if (cmp < 0)
              t = t.left;
          else if (cmp > 0)
              t = t.right;
          else
              return t.setValue(value);
      } while (t != null);
  }
  else {
      if (key == null)
          throw new NullPointerException();
      @SuppressWarnings("unchecked")
          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<>(key, value, parent);
  if (cmp < 0)
      parent.left = e;
  else
      parent.right = e;
  fixAfterInsertion(e);
  size++;
  modCount++;
  return null;
}
final int compare(Object k1, Object k2) {
   return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
       : comparator.compare((K)k1, (K)k2);
}

上面一段看起來很長,我們重看其中幾句就好了

  • TreeMap內置默認的Comparator比較器,一旦設定,后續所有的比較必須依據此比較器進行;發現不符合比較器方式的,拋出異常。
  • 核心的兩個do-while循環,依據比較器結果變量__cmp__,判斷是將值插入的覺提位置。一旦__cmp=0__將value放到相同位置。

相同的key會覆蓋,所有前面測試的時候,集合長度不增加,主要因為compareTo方法返回了0.


免責聲明!

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



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