前些天在網上偶然 間看到一篇關於java 8的 HashMap的分析文章,其對java7進行了大量改進,核心是引進了紅黑樹,提升了hashcode碰撞嚴重時的查找性能。這一點引起了我對紅黑樹的興趣。小白的我表示以前對2叉樹也是一之半解,更不要提複雜的紅黑樹了。所以下定決心分析下紅黑樹。html
首先介紹幾個我在這周過程當中在網上看到的一些比較好的文章以下:java
關於紅黑樹:算法
http://www.kuqin.com/shuoit/20160630/352539.html 對插入和刪除分析的比較到位數組
http://blog.csdn.net/v_july_v/article/details/6105630 比較詳細的介紹了紅黑樹的相關知數據結構
http://blog.csdn.net/v_JULY_v/article/details/6284050 july的系列文章,併發
http://www.cs.usfca.edu/~galles/visualization/Algorithms.html 算法的動態過程ide
java 中的數據結構分析HashMap TreeMap性能
http://tech.meituan.com/java-hashmap.html 美團 分析 的從新認識HashMapthis
http://www.importnew.com/21818.html TreeMap 實現spa
https://www.ibm.com/developerworks/cn/java/j-lo-tree/ TreeMap
http://yemengying.com/2016/05/07/threadsafe-hashmap/ HashMap併發性問題
接下來,對我這周的分析進行總結:
首先介紹下紅黑樹的相關知識,其是一顆平衡的二叉樹。其在二叉查找樹的基礎上增長了紅黑色和相關的性質使得紅黑樹相對平衡,從而保證了紅黑樹的查找、插入、刪除的時間複雜度最壞爲O(log n)。
具體的5個性質簡單要介紹,1)結點要麼爲黑要麼爲紅、2)根結點爲黑色、3)每一個葉節點(包括null節點)爲黑色、4)紅色結點的葉結點必須爲黑色、5)全部的節點到葉節點的黑結點數相同。
從上面五個性質,咱們能夠得出以下 結點能夠是連續黑色的,n個結點的高度爲logN。
紅黑樹的插入和刪除是經過結點的旋轉(左右旋轉),節點變色來從新平衡。接下來重點介紹下插入的恢復過程:
插入必爲紅色,其叔叔爲紅色,將叔,父變黑,祖父變紅。叔叔爲黑色,根據其父是左結點仍是右結點,進行相應的左旋與右旋。然後將父節點變黑,祖父變紅。如此直到根結點。
TreeMap 與HashMap
以前 我就在想,hashMap若是用到了紅黑樹,能夠直接引用treemap中的紅黑樹算法,但實際是HashMap中也本身實現了一套HashMap。首先分析下treeMap的算法實現:
TreeMap在構造有必定要求,若是在構造時沒有比較器。則在put時,若是key沒有實現comparable接口,則會拋出異常。所以要麼在構造時指定,要麼key實現comparable接口。
TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
接着就是經過一個標準的2叉樹查找實現,找到插入點。
Entry<K,V> t = root; 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);
查到插入點parent 後,插入節點,然後經過fixAfterInsertion(e)自修復
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;
修復過程以下:
首先將節點變爲紅色,
while (x != null && x != root && x.parent.color == RED) { if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { Entry<K,V> y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { 與上面相似,只是方向不一樣,這裏的邏輯對應的是父節點是祖父節點的右孩子, 因此若是當前節點是左孩子,先要右旋。造成兩個連續的紅色,接着祖父變紅,父變黑,由於以前是右孩子,左旋。 }} root.color = Baclk; }
最後當前結點爲root時,將root結點改成Black,插入結束。
刪除時相似,先找到節點,刪除後恢復,相似。
HashMap
HashMap 在1.8 以前 ,由一個鏈表的數組。不是一個單純的樹。而java8 後,一樣仍是數組,可是數組中的值有了變化,並必定是鏈表,默認的設置當前超過8時,鏈表轉換成紅黑樹的結構,可是仍是會保持一個以紅黑樹的root的節點爲鏈表頭的鏈表。
其中treeMap中與另一個與TreeMap中不一樣的是,key不要求實現comparable,或者指定比較器,而是用hashcode值 的大小來比較,注意不是hashcode的 轉換後,比數據長度&後的 值 ,由於同一個鏈表中的該值是相等的,而hashcode不必定同樣,可是若是 相等,則會經過 object對象的,以下方法,若是沒有重寫hashcode,則默認用以下方法
System.identityHashCode(a)
咱們重點介紹下數據節點是紅黑樹的插入狀況,
調用put 方法時,先將hashcode的高16位與低16位異或,讓高位也參與 位置的計算,減少hash衝突,然後與數組的大小進去位與運算,而數組的大小是2的冪,這樣的目的是爲了在擴容時,原來在在同一個數組的位置,要麼在原來的位置,要麼在原來位置的兩倍。減少hash運算。
接着若是計算出來的位置的節點 當前是紅黑樹,則就是紅黑樹的插入過程,可是不一樣的時,須要將計算出來的root節點,移動到鏈表的頭部。
而若是大小等於8時,就須要將鏈表轉換成結點,先要將每一個結點轉換成紅黑樹節點 ,然後依次插入,造成紅黑樹。刪除8時,須要從新轉換爲鏈表。
還有一個與java7 最大的不一樣就是 resize的過程。 java7 的實現 方式,從新遍歷每一個節點,從新計算插入擴容後的數組中,而java8 是沒有從新計算hashcode,而是將原hashcode後的hash值 與擴容後的那個增長位進行與運算,決定 數組的位置。
先介紹到這裏,後面在補充。