必修選修: 必修html
參考本博客:點擊進入對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果。java
實驗1:實現二叉樹的解決過程及結果
數組
實驗2:中序先序序列構造二叉樹的解決過程及結果
安全
實驗3:決策樹的解決過程及結果
數據結構
實驗4:表達式樹的解決過程及結果
app
實驗5:二叉查找樹的解決過程及結果
框架
實驗6 : 紅黑樹分析的解決過程及結果dom
寫在前面:剛找到TreeMap和HashMap的源碼,實際上是有些慌張不知所措的,靜下心來看一看,發現實際上是對不少方法的註釋很長,因此兩個源碼都是很長。函數
首先,咱們先要去了解Map是啥?Key是啥?而Value又是啥?源碼分析
在數組中咱們是經過數組下標來對其內容索引的,而在Map中咱們經過對象來對對象進行索引,用來索引的對象叫作key,其對應的對象叫作value。這就是平時說的鍵值對Key - value。
TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。TreeMap繼承於AbstractMap,因此它是一個Map,即一個key-value集合。TreeMap實現了NavigableMap接口,意味着它支持一系列的導航方法。好比返回有序的key集合。TreeMap實現了Cloneable接口,意味着它能被克隆。TreeMap實現了java.io.Serializable接口,意味着它支持序列化。TreeMap基於紅黑樹(Red-Blacktree)實現。該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。TreeMap的基本操做 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
// 默認構造函數。 HashMap() // 指定「容量大小」的構造函數 HashMap(int capacity) // 指定「容量大小」和「加載因子」的構造函數 HashMap(int capacity, float loadFactor) // 包含「子Map」的構造函數 HashMap(Map<? extends K, ? extends V> map)
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } // setter, getter, equals, toString 方法省略 public final int hashCode() { //用key的hash值與上value的hash值做爲Entry的hash值 return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } /** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */ void recordAccess(HashMap<K,V> m) { } /** * This method is invoked whenever the entry is * removed from the table. */ void recordRemoval(HashMap<K,V> m) { } }
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
public V get(Object key) { //單獨處理key爲null的狀況 if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } private V getForNullKey() { if (size == 0) { return null; } //key爲null的Entry用於放在table[0]中,可是在table[0]衝突鏈中的Entry的key不必定爲null //因此須要遍歷衝突鏈,查找key是否存在 for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); //首先定位到索引在table中的位置 //而後遍歷衝突鏈,查找key是否存在 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
private void inflateTable(int toSize) { //輔助函數,用於填充HashMap到指定的capacity // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); //threshold爲resize的閾值,超事後HashMap會進行resize,內容的entry會進行rehash threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); } /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. */ public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); //這裏的循環是關鍵 //當新增的key所對應的索引i,對應table[i]中已經有值時,進入循環體 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //判斷是否存在本次插入的key,若是存在用本次的value替換以前oldValue,至關於update操做 //並返回以前的oldValue if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //若是本次新增key以前不存在於HashMap中,modCount加1,說明結構改變了 modCount++; addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { //若是增長一個元素會後,HashMap的大小超過閾值,須要resize if ((size >= threshold) && (null != table[bucketIndex])) { //增長的幅度是以前的1倍 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { //首先獲得該索引處的衝突鏈Entries,有可能爲null,不爲null Entry<K,V> e = table[bucketIndex]; //而後把新的Entry添加到衝突鏈的開頭,也就是說,後插入的反而在前面(第一次還真沒看明白) //須要注意的是table[bucketIndex]自己並不存儲節點信息, //它就至關因而單向鏈表的頭指針,數據都存放在衝突鏈中。 table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } //下面看看HashMap是如何進行resize,廬山真面目就要揭曉了 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; //若是已經達到最大容量,那麼就直接返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; //initHashSeedAsNeeded(newCapacity)的返回值決定了是否須要從新計算Entry的hash值 transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; //遍歷當前的table,將裏面的元素添加到新的newTable中 for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; //最後這兩句用了與put放過相同的技巧 //將後插入的反而在前面 newTable[i] = e; e = next; } } } /** * Initialize the hashing mask value. We defer initialization until we * really need it. */ final boolean initHashSeedAsNeeded(int capacity) { boolean currentAltHashing = hashSeed != 0; boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); //這裏說明了,在hashSeed不爲0或知足useAltHash時,會重算Entry的hash值 //至於useAltHashing的做用能夠參考下面的連接 // http://stackoverflow.com/questions/29918624/what-is-the-use-of-holder-class-in-hashmap boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; }
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); //能夠看到刪除的key若是存在,就返回其所對應的value return (e == null ? null : e.value); } final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); //這裏用了兩個Entry對象,至關於兩個指針,爲的是防治衝突鏈發生斷裂的狀況 //這裏的思路就是通常的單向鏈表的刪除思路 Entry<K,V> prev = table[i]; Entry<K,V> e = prev; //當table[i]中存在衝突鏈時,開始遍歷裏面的元素 while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) //當衝突鏈只有一個Entry時 table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
// 默認構造函數。使用該構造函數,TreeMap中的元素按照天然排序進行排列。 TreeMap() // 建立的TreeMap包含Map TreeMap(Map<? extends K, ? extends V> copyFrom) // 指定Tree的比較器 TreeMap(Comparator<? super K> comparator) // 建立的TreeSet包含copyFrom TreeMap(SortedMap<K, ? extends V> copyFrom)
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
public boolean containsKey(Object key) { return getEntry(key) != null; }
public boolean containsValue(Object value) { for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e)) if (valEquals(value, e.value)) return true; return false; }
public V get(Object key) { Entry<K,V> p = getEntry(key); return (p==null ? null : p.value); }
public void putAll(Map<? extends K, ? extends V> map) { int mapSize = map.size(); if (size==0 && mapSize!=0 && map instanceof SortedMap) { Comparator<?> c = ((SortedMap<?,?>)map).comparator(); if (c == comparator || (c != null && c.equals(comparator))) { ++modCount; try { buildFromSorted(mapSize, map.entrySet().iterator(), null, null); } catch (java.io.IOException | ClassNotFoundException cannotHappen) { } return; } } super.putAll(map); }
final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
問題1的解決:經過詢問王文彬同窗,他教我理解了我代碼中存在的問題,實際上是由於我構造了兩個子樹,但卻沒有鏈接在一塊兒造成一個完整的樹,修改後將左子樹left
加入整個樹的構造中就能夠了。
問題2:在實驗二我理解完前序輸出和中序輸出的奧妙以後,終於在苦苦的編碼戰鬥中寫完了程序時,測試一下,結果卻很適合給學長學姐們國考加油!
解決過程:看圖說話
經過認真的屢次研究修改,終於個人決策樹完美出道了。
我以爲不少東西理解和代碼實現不是一回事,理解了我也不知道如何精確下手,可是在編寫的時候我又能更深入的理解好多遍。雖然過程及其「撕心裂肺」,可是仍是要多多受虐,才能在下次受虐的時候減輕疼痛。