這是看過的第一個jdk源碼(從立下目標以來):TreeMap。說實話斷斷續續的看了有好幾天了,我以爲我犯了一個錯誤,就像一開始說的那樣,我打算完徹底全看懂TreeMap關於紅黑樹的實現方式,後來我想了想,相對於花費這個對個人收益並非特別大,並且看的過程當中也有不少困惑,雖然我知道它每一步在作什麼,可是我還不知道爲何要這麼作,通過權衡,我決定先暫時放下TreeMap中關於對紅黑樹具體的實現,暫且記下有這回事。html
最近也在看數據結構和算法,對紅黑樹也有了大體的理解,TreeMap是對紅黑樹的Java實現完美實現版本。本篇不打算討論TreeMap是怎麼實現紅黑樹的。java
首先在紅黑樹中既要保存數據,還要保存數據所處的位置,天然而然就想到用一個對象來包裝存儲的數據,TreeMap中是用一個靜態最終類Entry來實現這個功能的,而這個類是實現自Map的Entry接口:算法
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left; Entry<K,V> right; Entry<K,V> parent; boolean color = BLACK; Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return valEquals(key,e.getKey()) && valEquals(value,e.getValue()); } public int hashCode() { int keyHash = (key==null ? 0 : key.hashCode()); int valueHash = (value==null ? 0 : value.hashCode()); return keyHash ^ valueHash; } public String toString() { return key + "=" + value; } }
能夠看到每個除了包含本身數據的key和value值,一樣有左、右、父節點的變量,幷包括一個默認的黑色字段,這樣,一個紅黑樹節點的全部信息就包含進去了,經過不斷擴充節點的left、right和parent來描述整個樹。編程
另外在equals中爲了比較兩個值是否相等還新建了一個比較方法:安全
static final boolean valEquals(Object o1, Object o2) { return (o1==null ? o2==null : o1.equals(o2)); }
這個比較方法避免了上層對null值的判斷,其實在Objects裏面已經有了,不過Objects出現的晚,因此說經常使用的工具方法就應該集中起來,否則每一個類都寫一遍可不是我心目中的jdk。數據結構
我對transient仍是有基本的瞭解的,當序列化對象的時候,被transient標記的字段就不會被序列化,所以我看到以下代碼時很驚訝:app
private transient Entry<K,V> root; private transient int size = 0;
我認爲根節點和容量是兩個很重要的屬性,序列化時爲何要去除呢,去除後會有錯誤的,爲了驗證個人想法我寫了個測試代碼:數據結構和算法
TreeMap<Integer, Integer> map=new TreeMap<>(); map.put(1, 1); map.put(2, 1); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); oos.writeObject(map); oos.flush(); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); TreeMap<Integer, Integer> mapClone = (TreeMap<Integer, Integer>) ois.readObject(); ois.close(); mapClone.put(3, 1); System.out.println(mapClone);
並在反序列化對象put新數據的時候,打斷點進去看,發現root竟然不是null,而size也不是0,也是真實容量2,我暈了,超出了個人理解。通過測試和查找沒有解決問題,在這留個坑,但願有人能幫到我。ide
以前我寫過一篇博客,是講解異常的,能夠去看一下:Java異常體系簡析。因此我對把異常捕獲而不作任何處理很是反感,所以看到如下代碼,以爲很是不適:工具
public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } }
什麼叫作cannotHappen,不可能發生那異常還有必要存在嗎,我仍是認爲應該拋一個虛擬機異常。不理解的能夠去看我上面說的那篇博客。
TreeMap不是一個線程安全的類,當你在遍歷一個TreeMap的時候,若是有另一個線程修改了TreeMap,會當即拋異常,是經過一個變量來實現的:
private transient int modCount = 0;
每次修改都會讓這個數加一,而後遍歷時每次都會判斷這個數是否發生變化:
@Override public void forEach(BiConsumer<? super K, ? super V> action) { Objects.requireNonNull(action); int expectedModCount = modCount; for (Entry<K, V> e = getFirstEntry(); e != null; e = successor(e)) { action.accept(e.key, e.value); if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } } }
這個能夠在必定程度上防止遍歷時被修改,只是被動防護,可是真要想達到隨意的增刪改查仍是要使用ConcurrentSkipListMap。
當咱們須要遍歷集合的時候,有一種作法是先獲取entrySet,而後遍歷entrySet的Entry並獲取key和value,在看到下面獲取entrySet的代碼時:
public Set<Map.Entry<K,V>> entrySet() { EntrySet es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); }
產生了疑惑,當獲取時若爲null就去建立一個new EntrySet(),而它的構造方法就是一個空的???,也就是說返回的確實是空的一個EntrySet,可是使用它的時候確實也有值:
TreeMap<Integer,Integer> map=new TreeMap<>(); map.put(1, 1); Set<Entry<Integer, Integer>> set = map.entrySet(); System.out.println(set);
我在map.entrySet()這一行,debug進去一步一步看,確實是沒有值,找了好一陣子,後來在debug這System.out.println(set)的時候才發現玄機。
簡單來講,當你得到entrySet的時候確實是沒有值的,可是它內部持有一個TreeMap的一個遍歷器,而在真正使用的時候纔去遍歷關聯的TreeMap,感受和jdk1.5的Stream同樣,所以方法的註釋也都說明是一個view。而TreeMap中大量應用了這種方式,我以爲其餘集合應該也是這麼實現的。
這就給咱們一種編程思路,不是說全部數據在獲取的時候都須要給它準備好,等他真正使用的時候再給也不晚。
TreeMap值有一些這樣的方法:
private static <K,V> boolean colorOf(Entry<K,V> p) { return (p == null ? BLACK : p.color); } private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) { return (p == null ? null: p.parent); } private static <K,V> void setColor(Entry<K,V> p, boolean c) { if (p != null) p.color = c; } private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) { return (p == null) ? null: p.left; } private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) { return (p == null) ? null: p.right; }
TreeMap中最經常使用的,判斷和獲取顏色,獲取左右節點等,雖然能夠經過【.】直接來得到屬性,可是有時候還必需要判斷是不是null值,因此當大量須要此處判斷的時候,封裝方法或許是咱們最好的選擇。
這個TreeMap代碼有3000多行,雖然也有不少註釋,代碼量依然很大,不過細看起來,只針對紅黑樹實現的增刪改查並無那麼多的代碼量,而多出來的實現,只是爲了方便使用者提供了各類各樣的方法,並所以而新建了不少內部類,而對於用戶來講只是感受到了方便。方法特別多,建議看Java API。稍微總結一點:
看到如今還遺留兩個問題:
這個我隨後會深刻測試研究。
這個我暫時不去研究,等之後須要本身實現的時候,再來找它。