JDK1.8原始碼(十一)——java.util.TreeMap類

YSOcean發表於2019-05-14

  在前面幾篇部落格分別介紹了這樣幾種集合,基於陣列實現的ArrayList 類,基於連結串列實現的LinkedList 類,基於雜湊表實現的HashMap 類,本篇部落格我們來介紹另一種資料型別,基於樹實現的TreeSet類。

1、TreeMap 定義

  聽名字就知道,TreeMap 是由Tree 和 Map 集合有關的,沒錯,TreeMap 是由紅黑樹實現的有序的 key-value 集合。

  PS:想要學懂TreeMap的實現原理,紅黑樹的瞭解是必不可少的!!!

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

  

  TreeMap 首先繼承了 AbstractMap 抽象類,表示它具有雜湊表的性質,也就是由 key-value 組成。

  其次 TreeMap 實現了 NavigableMap 介面,該介面支援一系列獲取指定集合的導航方法,比如獲取小於指定key的集合。

  最後分別實現 Serializable 介面以及 Cloneable 介面,分別表示支援物件序列化以及物件克隆

2、欄位定義

   ①、Comparator

    /**
     * The comparator used to maintain order in this tree map, or
     * null if it uses the natural ordering of its keys.
     *
     * @serial
     */
    private final Comparator<? super K> comparator;

  可以看上面的英文註釋,Comparator 是用來維護treemap集合中的順序,如果為null,則按照key的自然順序。

  Comparator 是一個介面,排序時需要實現其 compare 方法,該方法返回正數,零,負數分別代表大於,等於,小於。那麼怎麼使用呢?這裡舉個例子:

  這裡有一個Person類,裡面有兩個屬性pname,page,我們將該person物件放入ArrayList集合時,需要對其按照年齡進行排序。

 1 package com.ys.test;
 2 
 3 /**
 4  * Create by YSOcean
 5  */
 6 public class Person {
 7     private String pname;
 8     private Integer page;
 9 
10     public Person() {
11     }
12 
13     public Person(String pname, Integer page) {
14         this.pname = pname;
15         this.page = page;
16     }
17 
18     public String getPname() {
19         return pname;
20     }
21 
22     public void setPname(String pname) {
23         this.pname = pname;
24     }
25 
26     public Integer getPage() {
27         return page;
28     }
29 
30     public void setPage(Integer page) {
31         this.page = page;
32     }
33 
34     @Override
35     public String toString() {
36         return "Person{" +
37                 "pname='" + pname + '\'' +
38                 ", page=" + page +
39                 '}';
40     }
41 }
View Code

  排序程式碼:

 1 List<Person> personList = new ArrayList<>();
 2 personList.add(new Person("李四",20));
 3 personList.add(new Person("張三",10));
 4 personList.add(new Person("王五",30));
 5 System.out.println("原始順序為:"+personList.toString());
 6 Collections.sort(personList, new Comparator<Person>() {
 7     @Override
 8     public int compare(Person o1, Person o2) {
 9         //升序
10         //return o1.getPage()-o2.getPage();
11         //降序
12         return o2.getPage()-o1.getPage();
13         //不變
14         //return 0
15     }
16 });
17 System.out.println("排序後順序為:"+personList.toString());

  列印結果為:

  

  ②、Entry

private transient Entry<K,V> root;

  對於Entry詳細原始碼如下:

 1     static final class Entry<K,V> implements Map.Entry<K,V> {
 2         K key;
 3         V value;
 4         Entry<K,V> left;
 5         Entry<K,V> right;
 6         Entry<K,V> parent;
 7         boolean color = BLACK;
 8 
 9         /**
10          * Make a new cell with given key, value, and parent, and with
11          * {@code null} child links, and BLACK color.
12          */
13         Entry(K key, V value, Entry<K,V> parent) {
14             this.key = key;
15             this.value = value;
16             this.parent = parent;
17         }
18 
19         /**
20          * Returns the key.
21          *
22          * @return the key
23          */
24         public K getKey() {
25             return key;
26         }
27 
28         /**
29          * Returns the value associated with the key.
30          *
31          * @return the value associated with the key
32          */
33         public V getValue() {
34             return value;
35         }
36 
37         /**
38          * Replaces the value currently associated with the key with the given
39          * value.
40          *
41          * @return the value associated with the key before this method was
42          *         called
43          */
44         public V setValue(V value) {
45             V oldValue = this.value;
46             this.value = value;
47             return oldValue;
48         }
49 
50         public boolean equals(Object o) {
51             if (!(o instanceof Map.Entry))
52                 return false;
53             Map.Entry<?,?> e = (Map.Entry<?,?>)o;
54 
55             return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
56         }
57 
58         public int hashCode() {
59             int keyHash = (key==null ? 0 : key.hashCode());
60             int valueHash = (value==null ? 0 : value.hashCode());
61             return keyHash ^ valueHash;
62         }
63 
64         public String toString() {
65             return key + "=" + value;
66         }
67     }
View Code

  這裡主要看 Entry 類的幾個欄位:

K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;

  相信對紅黑樹這種資料結構瞭解的人,一看這幾個欄位就明白了,這也印證了前面所說的TreeMap底層有紅黑樹這種資料結構。

  ③、size

    /**
     * The number of entries in the tree
     */
    private transient int size = 0;

  用來表示entry的個數,也就是key-value的個數。

  ④、modCount

    /**
     * The number of structural modifications to the tree.
     */
    private transient int modCount = 0;

  基本上前面講的在ArrayList,LinkedList,HashMap等執行緒不安全的集合都有此欄位,用來實現Fail-Fast 機制,如果在迭代這些集合的過程中,有其他執行緒修改了這些集合,就會丟擲ConcurrentModificationException異常。

  ⑤、紅黑樹常量

    private static final boolean RED   = false;
    private static final boolean BLACK = true;

3、建構函式

  ①、無參建構函式

1     public TreeMap() {
2         comparator = null;
3     }

  將比較器 comparator 置為 null,表示按照key的自然順序進行排序。

  ②、帶比較器的建構函式

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

  需要自己實現Comparator。

  ③、構造包含指定map集合的元素

1     public TreeMap(Map<? extends K, ? extends V> m) {
2         comparator = null;
3         putAll(m);
4     }

  使用該構造器建立的TreeMap,會預設插入m表示的集合元素,並且comparator表示按照自然順序進行插入。

  ④、帶 SortedMap的建構函式

1     public TreeMap(SortedMap<K, ? extends V> m) {
2         comparator = m.comparator();
3         try {
4             buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
5         } catch (java.io.IOException cannotHappen) {
6         } catch (ClassNotFoundException cannotHappen) {
7         }
8     }

  和上面帶Map的建構函式不一樣,map是無序的,而SortedMap 是有序的,使用 buildFromSorted() 方法將SortedMap集合中的元素插入到TreeMap 中。

4、新增元素

 1     //新增元素
 2     public V put(K key, V value) {
 3         TreeMap.Entry<K,V> t = root;
 4         //如果根節點為空,即TreeMap中一個元素都沒有,那麼設定新新增的元素為根節點
 5         //並且設定集合大小size=1,以及modCount+1,這是用於快速失敗
 6         if (t == null) {
 7             compare(key, key); // type (and possibly null) check
 8 
 9             root = new TreeMap.Entry<>(key, value, null);
10             size = 1;
11             modCount++;
12             return null;
13         }
14         int cmp;
15         TreeMap.Entry<K,V> parent;
16         // split comparator and comparable paths
17         Comparator<? super K> cpr = comparator;
18         //如果比較器不為空,即初始化TreeMap建構函式時,有傳遞comparator類
19         //那麼插入新的元素時,按照comparator實現的類進行排序
20         if (cpr != null) {
21             //通過do-while迴圈不斷遍歷樹,呼叫比較器對key值進行比較
22             do {
23                 parent = t;
24                 cmp = cpr.compare(key, t.key);
25                 if (cmp < 0)
26                     t = t.left;
27                 else if (cmp > 0)
28                     t = t.right;
29                 else
30                     //遇到key相等,直接將新值覆蓋到原值上
31                     return t.setValue(value);
32             } while (t != null);
33         }
34         //如果比較器為空,即初始化TreeMap建構函式時,沒有傳遞comparator類
35         //那麼插入新的元素時,按照key的自然順序
36         else {
37             //如果key==null,直接丟擲異常
38             //注意,上面構造TreeMap傳入了Comparator,是可以允許key==null
39             if (key == null)
40                 throw new NullPointerException();
41             @SuppressWarnings("unchecked")
42             Comparable<? super K> k = (Comparable<? super K>) key;
43             do {
44                 parent = t;
45                 cmp = k.compareTo(t.key);
46                 if (cmp < 0)
47                     t = t.left;
48                 else if (cmp > 0)
49                     t = t.right;
50                 else
51                     return t.setValue(value);
52             } while (t != null);
53         }
54         //找到父親節點,根據父親節點建立一個新節點
55         TreeMap.Entry<K,V> e = new TreeMap.Entry<>(key, value, parent);
56         if (cmp < 0)
57             parent.left = e;
58         else
59             parent.right = e;
60         //修正紅黑樹(包括節點的左旋和右旋,具體可以看我Java資料結構和演算法中對紅黑樹的介紹)
61         fixAfterInsertion(e);
62         size++;
63         modCount++;
64         return null;
65     }

  新增元素,如果初始化TreeMap建構函式時,沒有傳遞comparator類,是不允許插入key==null的鍵值對的,相反,如果實現了Comparator,則可以傳遞key=null的鍵值對。

  另外,當插入一個新的元素後(除了根節點),會對TreeMap資料結構進行修正,也就是對紅黑樹進行修正,使其滿足紅黑樹的幾個特點,具體修正方法包括改變節點顏色,左旋,右旋等操作,這裡我不做詳細介紹了,具體可以參考我的這篇部落格:https://www.cnblogs.com/ysocean/p/8004211.html

5、刪除元素

  ①、根據key刪除

 1     public V remove(Object key) {
 2         //根據key找到該節點
 3         TreeMap.Entry<K,V> p = getEntry(key);
 4         if (p == null)
 5             return null;
 6         //獲取該節點的value,並返回
 7         V oldValue = p.value;
 8         //呼叫deleteEntry()方法刪除節點
 9         deleteEntry(p);
10         return oldValue;
11     }
12 
13     private void deleteEntry(TreeMap.Entry<K,V> p) {
14         modCount++;
15         size--;
16 
17         //如果刪除節點的左右節點都不為空,即有兩個孩子
18         if (p.left != null && p.right != null) {
19             //得到該節點的中序後繼節點
20             TreeMap.Entry<K,V> s = successor(p);
21             p.key = s.key;
22             p.value = s.value;
23             p = s;
24         } // p has 2 children
25 
26         // Start fixup at replacement node, if it exists.
27         TreeMap.Entry<K,V> replacement = (p.left != null ? p.left : p.right);
28         //待刪除節點只有一個子節點,直接刪除該節點,並用該節點的唯一子節點頂替該節點
29         if (replacement != null) {
30             // Link replacement to parent
31             replacement.parent = p.parent;
32             if (p.parent == null)
33                 root = replacement;
34             else if (p == p.parent.left)
35                 p.parent.left  = replacement;
36             else
37                 p.parent.right = replacement;
38 
39             // Null out links so they are OK to use by fixAfterDeletion.
40             p.left = p.right = p.parent = null;
41 
42             // Fix replacement
43             if (p.color == BLACK)
44                 fixAfterDeletion(replacement);
45 
46             //TreeMap中只有待刪除節點P,也就是隻有一個節點,直接返回nul即可
47         } else if (p.parent == null) { // return if we are the only node.
48             root = null;
49         } else { //  No children. Use self as phantom replacement and unlink.
50             //待刪除節點沒有子節點,即為葉子節點,直接刪除即可
51             if (p.color == BLACK)
52                 fixAfterDeletion(p);
53 
54             if (p.parent != null) {
55                 if (p == p.parent.left)
56                     p.parent.left = null;
57                 else if (p == p.parent.right)
58                     p.parent.right = null;
59                 p.parent = null;
60             }
61         }
62     }

  刪除節點分為四種情況:

  1、根據key沒有找到該節點:也就是集合中不存在這一個節點,直接返回null即可。

  2、根據key找到節點,又分為三種情況:

    ①、待刪除節點沒有子節點,即為葉子節點:直接刪除該節點即可。

    ②、待刪除節點只有一個子節點:那麼首先找到待刪除節點的子節點,然後刪除該節點,用其唯一子節點頂替該節點。

    ③、待刪除節點有兩個子節點:首先找到該節點的中序後繼節點,然後把這個後繼節點的內容複製給待刪除節點,然後刪除該中序後繼節點,刪除過程又轉換成前面①、②兩種情況了,這裡主要是找到中序後繼節點,相當於待刪除節點的一個替身。

6、查詢元素

  ①、根據key查詢

 1     public V get(Object key) {
 2         TreeMap.Entry<K,V> p = getEntry(key);
 3         return (p==null ? null : p.value);
 4     }
 5 
 6     final TreeMap.Entry<K,V> getEntry(Object key) {
 7         // Offload comparator-based version for sake of performance
 8         if (comparator != null)
 9             return getEntryUsingComparator(key);
10         if (key == null)
11             throw new NullPointerException();
12         @SuppressWarnings("unchecked")
13         Comparable<? super K> k = (Comparable<? super K>) key;
14         TreeMap.Entry<K,V> p = root;
15         while (p != null) {
16             int cmp = k.compareTo(p.key);
17             if (cmp < 0)
18                 p = p.left;
19             else if (cmp > 0)
20                 p = p.right;
21             else
22                 return p;
23         }
24         return null;
25     }

7、遍歷元素

  通常有下面兩種方法,第二種方法效率要快很多。

 1 TreeMap<String,Integer> map = new TreeMap<>();
 2 map.put("A",1);
 3 map.put("B",2);
 4 map.put("C",3);
 5 
 6 //第一種方法
 7 //首先利用keySet()方法得到key的集合,然後利用map.get()方法根據key得到value
 8 Iterator<String> iterator = map.keySet().iterator();
 9 while(iterator.hasNext()){
10     String key = iterator.next();
11     System.out.println(key+":"+map.get(key));
12 }
13 
14 //第二種方法
15 Iterator<Map.Entry<String,Integer>> iterator1 = map.entrySet().iterator();
16 while(iterator1.hasNext()){
17     Map.Entry<String,Integer> entry = iterator1.next();
18     System.out.println(entry.getKey()+":"+entry.getValue());
19 }

 

相關文章