TreeMap put()和遍歷

這篇博客記錄了學習TreeMap時關於put()方法和遍歷TreeMap的解析,方便鞏固學習,僅作參考。

 

TreeMap<String, String> treeMap = new TreeMap<>() ;
String str1 = "12";
String str2 = "123";
String str3 = "1234";
treeMap.put(str1, "1");
treeMap.put(str2, "2");
treeMap.put(str3, "3");
System.out.println("TreeMap not set iterator");
Iterator<Entry<String, String>> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
	Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
	System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}

控制檯打印結果: 

TreeMap not set iterator
[12 ,1]
[123 ,2]
[1234 ,3]

在解析源碼之前,粗略解釋一下Comparable接口實現:

實現了Comparable接口的類的實例之間是可以相互比較的。在實現Comparable時,會將實例的一個特徵或多個特徵作爲重要判定依據,按照某種方法得出的值(這裏假設爲compareVal),實例間做相互比較。如果實例A的compareVal 大於 實例B的compareVal, A.compareTo(B)返回 1, B.compareTo(A)返回 -1,則對實例的排序是升序的。如果實例A的compareVal 大於 實例B的compareVal, A.compareTo(B)返回 -1, B.compareTo(A)返回 1,則對實例的排序是降序的。

舉一個簡單的例子:

 Person類

public class Person implements Comparable{
    int age;
    String name;
    ...
    // 按照age來比較(升序)
    public int compareTo(Person person) {
        
        return this.age - person.age;
    }
    
    /**
     * // 按照age來比較 (降序)
     * public int compareTo(Person person) {
     *     return person.age - this.age;   
     * }
     */
}

TreeMap put(K key, V value)源碼解析

public V put(K key, V value) {
        Entry<K,V> t = root;
        // 第一次向容器中放入元素(k/v)時,創建根節點(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;

        // 默認的比較器(comparator)是空的,在創建TreeMap容器時傳入一個比較器。
        // 所以按照上面代碼創建TreeMap容器,比較器是空的。
        // 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();
            // 如果沒有設定比較器,就使用Key對應類的比較方式。所以,下面這行代碼就是得到
            // key所實現的比較方式。key先跟父節點(最開始時父節點就是根節點root)的key比較,小於則 
            // 繼續跟父節點的左子節點比較,大於則跟父節點的右子節點,以此類推,直到找到與e的key相
            // 等的節點或者父節點的左子節點或者右子節點爲null。      
            // 當找到與e的key相等的節點時,替換該節點的value,當父節點的    
            // 左子節點(右子節點)爲null時,就創建一個節點作爲父節點的左子節點(右子節點)

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

TreeMap遍歷,源碼解析:

Iterator<Entry<String, String>> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
	Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next(); 		
}

treeMap.entrySet().iterator()內部源碼調用順序: 

->  TreeMap.entrySet()  返回一個EntrySet的集合

->  new EntrySet() 

->  EntrySet.iterator()  返回一個基於EntrySet集合的迭代器EntryIterator

->   new EntryIterator() 

->  TreeMap.getFirstEntry() 得到整個樹的最左邊的節點

iterator.next()內部源碼調用順序: 

->  EntryIterator.next()

->  PrivateEntryIterator.nextEntry()

->  successor(e)  整個迭代器最主要的迭代代碼在successor(e)中

// 得到Entry的set集合
public Set<Map.Entry<K,V>> entrySet() {
    EntrySet es = entrySet;
    return (es != null) ? es : (entrySet = new EntrySet());
}
// TreeMap的內部類EntrySet 
class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return new EntryIterator(getFirstEntry());                                        
        }
}
// TreeMap內部類 EntryIterator (迭代器)
// EntryIterator 繼承PrivateEntryIterator類
final class EntryIterator extends PrivateEntryIterator<Map.Entry<K,V>> {
        EntryIterator(Entry<K,V> first) {
            super(first);
        }
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
 }

 // PrivateEntryIterator
 abstract class PrivateEntryIterator<T> implements Iterator<T> {
        Entry<K,V> next;
        Entry<K,V> lastReturned;
        int expectedModCount;

        PrivateEntryIterator(Entry<K,V> first) {
            expectedModCount = modCount;
            lastReturned = null;
            next = first; // 初始化EntryIterator時,next指向整個樹的最左邊的葉子節點
        }
 
  public final boolean hasNext() {
      return next != null; // 當沒有節點時,迭代終止。
  }
  
  final Entry<K,V> nextEntry() {
      Entry<K,V> e = next; // 第一次調用這個方法時,e指向整個樹的最左邊的葉子節點
      if (e == null)
          throw new NoSuchElementException();
      if (modCount != expectedModCount)
          throw new ConcurrentModificationException();
      // 整個迭代器最主要的迭代代碼在successor(e)中,successor方法返回下一個節點next,下一次遍    
      // 歷時,next又用作參數e,作爲基礎節點來尋找下一個返回節點。
      next = successor(e); 
      lastReturned = e;
      return e; // 執行successor方法後返回e。
   }

}


final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
 }

 TreeMap的getFirstEntry()方法,找到整個樹的最左邊的葉子節點,參考下圖:即節點 D

                   A
          B                   C
    D          E        F        G
   


static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        // 第一次調用時 t 指向整個樹的最左邊的葉子節點 D(如上圖)
        if (t == null)
            return null;
        // 如果節點t有右節點t_right,但是t_right 沒有左節點,就返回t_right(即t的右節點)
        // 如果節點t有右節點t_right,並且t_right有左節點t_right_left,就以t_right爲父節點,
        // 返回其下最左邊的子節點。
        else if (t.right != null) { 
            Entry<K,V> p = t.right; 
            while (p.left != null)
                p = p.left;
            return p;
        } else {
            // 節點t沒有右節點
            // 並且t不是一個右子節點,就返回t的父節點
            // 如果節點t是一個右節點,就返回t的父節點的父節點
            Entry<K,V> p = t.parent; 
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p; //注意這裏返回的節點P用作下一次迭代的基礎,而不是iterator.next()的返回值
                      // iterator.next()返回值是節點 t
        }
    }

以上圖所示的樹結構圖爲例,以iterator.next()的調用順序爲例:

調用前next指的是調用前next指向的節點

調用後next指的是調用後next指向的節點

次數 調用前next 調用後next 返回節點 分析
1 D B D D節點沒有右節點,但是D節點不是一個右節點,所以調用後next()方法後,next指向D的父節點B。
2 B E B B節點有右節點E,但是E節點沒有左節點,所以調用後next()方法後,next指向B的右節點E。
3 E A E E沒有右節點,並且B節點是一個右節點,所以調用後next()方法後,next指向E的父節點B的父節點A。

依次類推,整個樹的遍歷結果是DBEAFCG。

根據TreeMap的遍歷代碼可以看出遍歷的順序是左節點 -> 父節點 -> 右節點,是中序遍歷。

自定義TreeMap的比較器

通過自定義比較器來控制整個樹的節點順序。這裏爲了偷懶,採用與String排序相反的方式。

TreeMap<String, String> treeMapWithIterator = new TreeMap<>((x, y)->{
			return y.compareTo(x);
});
treeMapWithIterator.put(str1, "1");
treeMapWithIterator.put(str2, "2");
treeMapWithIterator.put(str3, "3");
System.out.println("降序:");
// TreeMap是用中序遍歷
Iterator<Entry<String, String>> iterator2 = treeMapWithIterator.entrySet().iterator();
while (iterator2.hasNext()) {
		Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator2.next();
		System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}

下面是沒有自定義比較器的測試,默認使用String實例的比較方式:

TreeMap<String, String> treeMap = new TreeMap<>() ;
String str1 = "12";
String str2 = "123";
String str3 = "1234";
treeMap.put(str1, "1");
treeMap.put(str2, "2");
treeMap.put(str3, "3");
System.out.println("升序:");
Iterator<Entry<String, String>> iterator = treeMap.entrySet().iterator();
while (iterator.hasNext()) {
	Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
	System.out.println("[" + entry.getKey() + " ," + entry.getValue() + "]");
}

看看兩者的輸出結果對比:

升序
[12 ,1]
[123 ,2]
[1234 ,3]
降序:
[1234 ,3]
[123 ,2]
[12 ,1]

 在構建TreeMap時如果沒設定比較器,那麼往TreeMap中放入元素時就會使用Key對應的類實現的比較方式。
 如果既沒有設定比較器,Key對應的類也未實現Comparable接口,那麼就會出現ClassCastException
 參考put()方法的這行代碼:    Comparable<? super K> k = (Comparable<? super K>) key

僅作參考,有疏漏或者不正確的地方歡迎指出,相互學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章