在前面幾篇部落格分別介紹了這樣幾種集合,基於陣列實現的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 }
排序程式碼:
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 }
這裡主要看 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 }