🔎【Java源碼探索】深入淺出的分析HashMap(JDK8)

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"【每日一句】","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一個人最大的挑戰,是如何去克服自己的缺點。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"【基本原理】","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap是一個基於map接口實現的散列表,存儲內容是鍵值對 (key-value) 映射,並且鍵和值都可以使用null,因爲key不允許重複,因此只能有一個鍵爲null","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap使用 hash 算法進行數據的存儲和查詢。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap的實現用的是數組+鏈表+紅黑樹的結構,也叫哈希桶。在jdk 1.8之前都是數組+鏈表的結構,因爲在鏈表的查詢操作都是O(N)的時間複雜度,如果當節點數量多,轉換爲紅黑樹結構,那麼將會提高很大的效率,因爲紅黑樹結構中,增刪改查都是O(log n)。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"【基本特性】","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap的散列表是懶加載機制,在第一次put的時候纔會創建","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"它根據鍵的 hashCode 值存儲數據,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap 最多隻允許一條記錄的鍵(Key)爲 null,允許多條記錄的值爲 null","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap是無序不重複的,而且HashMap是線程不安全的。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap 默認情況下使用一個Entry表示鍵值對 key-value,用Entry的數組保存所有鍵值對","attrs":{}},{"type":"text","text":",Entry通過鏈表的方式鏈接後續的節點 (1.8 後會根據鏈表長度決定是否轉換成一棵樹類似TreeMap來節省查詢時間,Node節點會採用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LinkedHashMapEntry的屬性","attrs":{}},{"type":"text","text":"),Entry通過計算 key 的 hash 值來決定映射到具體的哪個數組(也叫 Bucket) 中。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap 非線程安全,即任一時刻可以有多個線程同時寫 HashMap","attrs":{}},{"type":"text","text":",可能會導致數據的不一致。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果需要滿足線程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有線程安全的能力,或者使用 ConcurrentHashMap。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"【優化點】","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"鏈表改爲紅黑樹:時間複雜度(O(N) —> O(log(N)))","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"【原理簡述】","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"【put方法】","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"輸入參數","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"key值,value值","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"運作流程","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首先針對於傳入的對Key求hash值,然後再計算下標","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果沒有碰撞,直接放入桶中(碰撞的意思是計算得到的hash值相同,需要放到同一個bucket中,代表着屬於鏈表)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果hash值發生碰撞後,以鏈表的方式鏈接到後面","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果鏈表長度超過閥值( TREEIFY THRESHOLD==8),就把鏈表轉成紅黑樹,鏈表長度低於6,就把紅黑樹轉回鏈表。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果key的hashcode相同且value也相同的情況下,就替換舊值","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果桶到達閾值(Threshold)後(初始化容量(16)以及加載因子(0.75)),就需要 resize(擴容2倍後並且進行重排(重新hash和重新排版))","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"數據結構圖","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5a/5a66cffabf4f896216f324d705ad1de2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"HashMap屬性代碼","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首先,需要記住的是,JCF的一個傳統模式,就是集成AbstractXXX抽象類和實現所有的基礎接口XXX,XXX(Map,List,Set,Collection等)","attrs":{}},{"type":"text","text":",並且可以實現","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"序列化和克隆","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"屬性默認值","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class HashMap extends AbstractMap\n implements Map, Cloneable, Serializable {\n //序列號,序列化的時候使用\n private static final long serialVersionUID = 362498820763181265L;\n //默認容量,爲2的4次方,即爲16, 必須爲 2 的 n 次方 (一定是合數)\n static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;\n //最大容量,爲2的30次方。\n static final int MAXIMUM_CAPACITY = 1 << 30;\n //加載因子,用於擴容使用。\n static final float DEFAULT_LOAD_FACTOR = 0.75f;\n //鏈表轉成紅黑樹的閾值。即在哈希表擴容時,當鏈表的長度(桶中元素個數)超過這個值的時候,進行鏈表到紅黑樹的轉變\n static final int TREEIFY_THRESHOLD = 8;\n //紅黑樹轉爲鏈表的閾值。即在哈希表擴容時,如果發現鏈表長度(桶中元素個數)小於 6,則會由紅黑樹重新退化爲鏈表\n static final int UNTREEIFY_THRESHOLD = 6;\n //當整個hashMap中元素數量大於64時,也會進行轉爲紅黑樹結構。\n //HashMap 的最小樹形化容量。這個值的意義是:位桶(bin)處的數\n //據要採用紅黑樹結構進行存儲時,整個Table的最小容量(存儲方式由\n //鏈表轉成紅黑樹的容量的最小閾值) 當哈希表中的容量大於這個值\n //時,表中的桶才能進行樹形化,否則桶內元素太多時會擴容,而不是\n // 樹形化爲了避免進行擴容、樹形化選擇的衝突,這個值不能小於 4 *\n // TREEIFY_THRESHOLD\n static final int MIN_TREEIFY_CAPACITY = 64;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"屬性參數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class HashMap extends AbstractMap\n implements Map, Cloneable, Serializable {\n\n transient Node[] table;\n //將數據轉換成set的另一種存儲形式,這個變量主要用於迭代功能。\n transient Set> entrySet;\n //元素數量\n transient int size;\n //統計該map修改的次數,用來記錄 HashMap 內部結構發生變化的次數,主要用於迭代的快速失敗機制\n transient int modCount;\n //HashMap 的門限閥值/擴容閾值,所能容納的 key-value 鍵值對極 //\n // 限,當size>=threshold時,就會擴容,計算方法:容量capacity * 負\n // 載因子load factor 。\n int threshold;\n //加載因子\n final float loadFactor;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Node[] table:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"的初始化長度 length(默認值是 16),loadFactor 爲負載因子 (默認值 DEFAULT_LOAD_FACTOR 是 0.75),threshold 是 HashMap 所能容納的最大數據量的 Node(鍵值對) 個數","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"threshold = length * loadFactor。也就是說,在數組定義好長度之後,負載因子越大,所能容納的鍵值對個數越多。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"**這裏我們需要加載因子 (load_factor),加載因子默認爲 0.75,當 HashMap 中存儲的元素的數量大於 (容量 × 加載因子),也就是默認大於 16*0.75=12 時,HashMap 會進行擴容的操作 **。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"size:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這個字段其實很好理解,就是HashMap中實際存在的鍵值對數量","attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意和 table 的長度 length、容納最大鍵值對數量 threshold 的區別。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"modCount:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"字段主要用來記錄HashMap內部結構發生變化的次數,主要用於迭代的快速失敗。強調一點,內部結構發生變化指的是結構發生變化。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"put新鍵值對,某個key對應的value值被覆蓋不屬於結構變化。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HashMap的內部功能實現很多,本文主要從根據key獲取哈希桶數組索引位置、put方法的詳細執行、擴容過程等具有代表性的點深入展開講解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"構造函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第一個默認初始化+默認加載因子,第二個設置初始容量+初始化默認加載因子,第三個設置初始容量和加載因子","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" // 默認初始化容量+默認負載因子\n public HashMap() {\n this.loadFactor = DEFAULT_LOAD_FACTOR;\n }\n // 自定義初始化容量+默認負載因子\n public HashMap(int initialCapacity) {\n this(initialCapacity, DEFAULT_LOAD_FACTOR);\n }\n // 自定義初始化容量以及負載因子\n public HashMap(int initialCapacity, float loadFactor) {\n if (initialCapacity < 0)\n throw new IllegalArgumentException(\"Illegal initial capacity: \" +\n initialCapacity);\n if (initialCapacity > MAXIMUM_CAPACITY)\n initialCapacity = MAXIMUM_CAPACITY;\n if (loadFactor <= 0 || Float.isNaN(loadFactor))\n throw new IllegalArgumentException(\"Illegal load factor: \" +\n loadFactor);\n this.loadFactor = loadFactor;\n this.threshold = tableSizeFor(initialCapacity);\n }\n\n public HashMap(Map extends K, ? extends V> m) {\n this.loadFactor = DEFAULT_LOAD_FACTOR;\n putMapEntries(m, false);\n }\n \n final void putMapEntries(Map extends K, ? extends V> m, boolean \n evict) {\n //獲取該map的實際長度\n int s = m.size();\n if (s > 0) {\n //判斷table是否初始化,如果沒有初始化\n if (table == null) { // pre-size\n /**求出需要的容量,因爲實際使用的長度=容量*0.75得來的,+1是因爲小數相除,基本都不會是整數,容量大小不能爲小數的,後面轉換爲int,多餘的小數就要被丟掉,所以+1,例如,map實際長度22,22/0.75=29.3,所需要的容量肯定爲30,有人會問如果剛剛好除得整數呢,除得整數的話,容量大小多1也沒什麼影響**/\n float ft = ((float)s / loadFactor) + 1.0F;\n //判斷該容量大小是否超出上限。\n int t = ((ft < (float)MAXIMUM_CAPACITY) ?\n (int)ft : MAXIMUM_CAPACITY);\n /**對臨界值進行初始化,tableSizeFor(t)這個方法會返回大於t值的,且離其最近的2次冪,例如t爲29,則返回的值是32**/\n if (t > threshold)\n threshold = tableSizeFor(t);\n }\n //如果table已經初始化,則進行擴容操作,resize()就是擴容。\n else if (s > threshold)\n resize();\n //遍歷,把map中的數據轉到hashMap中。\n for (Map.Entry extends K, ? extends V> e : m.entrySet()) {\n K key = e.getKey();\n V value = e.getValue();\n putVal(hash(key), key, value, false, evict);\n }\n }\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"節點對象","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap內部類TreeNode,該類是一個紅黑樹結構","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static final class TreeNode extends\n LinkedHashMap.LinkedHashMapEntry {\n // red-black tree links\n TreeNode parent;\n TreeNode left;\n TreeNode right;\n // needed to unlink next upon deletion\n TreeNode prev;\n boolean red;\n TreeNode(int hash, K key, V val, Node next) {\n super(hash, key, val, next);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap內部類Node, 結構爲單向鏈表。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static class Node implements Map.Entry {\n final int hash;\n final K key;\n V value;\n Node next;\n Node(int hash, K key, V value, Node next) {\n this.hash = hash;\n this.key = key;\n this.value = value;\n this.next = next;\n }\n public final K getKey() { return key; }\n public final V getValue() { return value; }\n public final String toString() { return key + \"=\" + value; }\n public final int hashCode() {\n return Objects.hashCode(key) ^ Objects.hashCode(value);\n }\n public final V setValue(V newValue) {\n V oldValue = value;\n value = newValue;\n return oldValue;\n }\n public final boolean equals(Object o) {\n if (o == this)\n return true;\n if (o instanceof Map.Entry) {\n Map.Entry,?> e = (Map.Entry,?>)o;\n if (Objects.equals(key, e.getKey()) &&\n Objects.equals(value, e.getValue()))\n return true;\n }\n return false;\n }\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"哈希方法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"解決Hash 的的衝突的hash()方法,HashMap的hash計算時先計算 hashCode(), 然後進行二次hash","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 計算二次Hash\nint hash = hash(key.hashCode());\n// 通過Hash找數組索引\nint i = hash & (tab.length-1);\n\nstatic final int hash(Object key) {\n int h;\n // 先獲取到key的hashCode,然後進行移位再進行異或運算,爲什麼這\n //麼複雜,不用想肯定是爲了減少hash衝突\n return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這個方法非常巧妙,它總是通過 h &(table.length -1) 來得到該對象的保存位置,而 HashMap 底層數組的長度總是 2 的 n 次方。當 length 總是 2 的倍數時,h & (length-1) 將是一個非常巧妙的設計","attrs":{}},{"type":"text","text":":","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設 h=5,length=16, 那麼 h & length - 1 將得到 5;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設 h=6,length=16, 那麼 h & length - 1 將得到 6","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設 h=15,length=16, 那麼 h & length - 1 將得到 15;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"但是當 h=16 時 , length=16 時,那麼 h & length - 1 將得到 0 了;當 h=17 時 , length=16 時,那麼 h & length - 1 將得到 1 了。這樣保證計算得到的索引值總是位於 table 數組的索引之內","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"添加元素","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public V put(K key, V value) {\n // 對key的hashCode()做hash\n return putVal(hash(key), key, value, false, true);\n}\n\n/**\n * Implements Map.put and related methods\n *\n * @param hash hash for key\n * @param key the key\n * @param value the value to put\n * @param onlyIfAbsent if true, don't change existing value\n * @param evict if false, the table is in creation mode.\n * @return previous value, or null if none\n */\nfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n boolean evict) {\n Node[] tab; Node p; int n, i;\n // table爲空或者length=0時,以默認大小擴容,n爲table的長度 \n if ((tab = table) == null || (n = tab.length) == 0)\n n = (tab = resize()).length;\n // 計算index,並對null做處理,table[i]==null\n if ((p = tab[i = (n - 1) & hash]) == null)\n // (n-1)&hash 與Java7中indexFor方法的實現相同,若i位置上的值爲空,則新建一個Node,table[i]指向該Node。\n // 直接插入\n tab[i] = newNode(hash, key, value, null);\n else {\n // 若i位置上的值不爲空,判斷當前位置上的Node p 是否與要插入的key的hash和key相同\n Node e; K k;\n // 若節點key存在,直接覆蓋value\n if (p.hash == hash &&\n ((k = p.key) == key || (key != null && key.equals(k))))\n e = p;\n // 判斷table[i]該鏈是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對\n else if (p instanceof TreeNode)\n // 不同,且當前位置上的的node p已經是TreeNode的實例,則再該樹上插入新的node\n e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);\n // table[i]該鏈是普通鏈表,進行鏈表的插入操作\n else {\n // 在i位置上的鏈表中找到p.next爲null的位置,binCount計算出當前鏈表的長度,如果繼續將衝突的節點插入到該鏈表中,會使鏈表的長度大於tree化的閾值,則將鏈表轉換成tree。\n for (int binCount = 0; ; ++binCount) {\n // 如果遍歷到了最後一個節點,說明沒有匹配的key,則創建一個新的節點並添加到最後\n if ((e = p.next) == null) {\n p.next = newNode(hash, key, value, null);\n // 鏈表長度大於8轉換爲紅黑樹進行處理\n if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st\n treeifyBin(tab, hash);\n break;\n }\n // 遍歷過程中若發現 key 已經存在直接覆蓋 value 並跳出循環即可\n if (e.hash == hash &&\n ((k = e.key) == key || (key != null && key.equals(k))))\n break;\n p = e;\n }\n }\n // 已經存在該key的情況時,將對應的節點的value設置爲新的value\n if (e != null) { // existing mapping for key\n V oldValue = e.value;\n if (!onlyIfAbsent || oldValue == null)\n e.value = value;\n afterNodeAccess(e);\n return oldValue;\n }\n }\n ++modCount;\n // 插入成功後,判斷實際存在的鍵值對數量 size 是否超多了最大容量 threshold,如果超過,進行擴容\n if (++size > threshold)\n resize();\n afterNodeInsertion(evict);\n return null;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"紅黑樹結構的putVal方法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"final TreeNode putTreeVal(HashMap map, Node[] tab,int h, K k, V v) {\n Class> kc = null;\n boolean searched = false;\n TreeNode root = (parent != null) ? root() : this;\n for (TreeNode p = root;;) {\n int dir, ph; K pk;\n if ((ph = p.hash) > h)\n dir = -1;\n else if (ph < h)\n dir = 1;\n else if ((pk = p.key) == k || (k != null && k.equals(pk)))\n return p;\n else if ((kc == null &&\n (kc = comparableClassFor(k)) == null) ||\n (dir = compareComparables(kc, k, pk)) == 0) {\n if (!searched) {\n TreeNode q, ch;\n searched = true;\n if (((ch = p.left) != null &&\n (q = ch.find(h, k, kc)) != null) ||\n ((ch = p.right) != null &&\n (q = ch.find(h, k, kc)) != null))\n return q;\n }\n dir = tieBreakOrder(k, pk);\n }\n TreeNode xp = p;\n if ((p = (dir <= 0) ? p.left : p.right) == null) {\n Node xpn = xp.next;\n TreeNode x = map.newTreeNode(h, k, v, xpn);\n if (dir <= 0)\n xp.left = x;\n else\n xp.right = x;\n xp.next = x;\n x.parent = x.prev = xp;\n if (xpn != null)\n ((TreeNode)xpn).prev = x;\n moveRootToFront(tab, balanceInsertion(root, x));\n return null;\n }\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結 put()方法大致的思路爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對key的hashCode()做hash,然後再計算 index;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果沒碰撞直接放到 bucket裏;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果碰撞了,以鏈表的形式存在 buckets 後;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果碰撞導致鏈表過長 (大於等於 TREEIFY_THRESHOLD=8),就把鏈表轉換成紅黑樹;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果節點已經存在就替換 old value(保證 key 的唯一性)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果 bucket 滿了 (超過 load factor*current capacity),就要 resize","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"具體步驟爲","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果 table 沒有使用過的情況(tab=table)==null || (n=tab.length) == 0,則以默認大小進行一次 resize","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"計算key的hash值,然後獲取底層 table 數組的第 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"(n-1) & hash","attrs":{}},{"type":"text","text":" 的位置的數組索引tab[i] 處的數據,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"即hash對n取模的位置,依賴的是n 爲2的次方這一條件","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"先檢查該 bucket 第一個元素是否是和插入的 key 相等 (如果是同一個對象則肯定 equals)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果不相等並且是 TreeNode 的情況,調用TreeNode 的 put 方法否則循環遍歷樹節點,","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果找到相等的key跳出循環否則達到最後一個節點時將新的節點添加到鏈表最後, 當前面找到了相同的 key 的情況下替換這個節點的 value 爲新的 value。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"最後如果新增了 key-value 對,則增加 size 並且判斷是否超過了 threshold, 如果超過則需要進行 resize 擴容","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"擴容尺寸","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"擴容 (resize) 就是重新計算容量,向HashMap對象裏不停的添加元素,而HashMap對象內部的數組無法裝載更多的元素時,對象就需要擴大數組的長度,以便能裝入更多的元素","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當然Java裏的數組是無法自動擴容的,方法是使用一個新的數組代替已有的容量小的數組,就像我們用一個小桶裝水,如果想裝更多的水,就得換大水桶","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"由於需要考慮hash衝突解決時採用的可能是鏈表也可能是紅黑樹的方式,因此resize方法相比JDK7 中複雜了一些","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"rehashing觸發的條件:","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"超過默認容量 * 加載因子","attrs":{}},{"type":"text","text":";","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"加載因子不靠譜,比如遠大於 1","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在HashMap進行擴容時,會進行2倍擴容,而且會將哈希碰撞處的數據再次分散開來,一部分依照新的 hash 索引值呆在 “原處”,另一部分加上偏移量移動到新的地方","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體步驟爲:","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首先計算 resize() 後的新的 capacity 和 threshold 值","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果原有的 capacity 大於零則將 capacity 增加一倍,否則設置成默認的 capacity","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"創建新的數組,大小是新的capacity","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"將舊數組的元素放置到新數組中","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"final Node[] resize() {\n // 將字段引用copy到局部變量表,這樣在之後的使用時可以減少getField指令的調用\n Node[] oldTab = table;\n // oldCap爲原數組的大小或當空時爲0\n int oldCap = (oldTab == null) ? 0 : oldTab.length;\n int oldThr = threshold;\n int newCap, newThr = 0;\n if (oldCap > 0) {\n if (oldCap >= MAXIMUM_CAPACITY) {\n // 如果超過最大容量1>>30,無法再擴充table,只能改變閾值\n threshold = Integer.MAX_VALUE;\n return oldTab;\n }\n // 新的數組的大小是舊數組的兩倍\n else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&\n oldCap >= DEFAULT_INITIAL_CAPACITY)\n // 當舊的的數組大小大於等於默認大小時,threshold也擴大一倍\n newThr = oldThr << 1;\n }\n else if (oldThr > 0) \n // initial capacity was placed in threshold\n newCap = oldThr;\n else { \n // zero initial threshold signifies using defaults\n // 初始化操作\n newCap = DEFAULT_INITIAL_CAPACITY;\n newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);\n }\n if (newThr == 0) {\n float ft = (float)newCap * loadFactor;\n newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?\n (int)ft : Integer.MAX_VALUE);\n }\n threshold = newThr;\n @SuppressWarnings({\"rawtypes\",\"unchecked\"})\n // 創建容量爲newCap的newTab,並將oldTab中的Node遷移過來,這裏需要考慮鏈表和tree兩種情況。\n Node[] newTab = (Node[])new Node[newCap];\n table = newTab;\n // 將原數組中的數組複製到新數組中\n if (oldTab != null) {\n for (int j = 0; j < oldCap; ++j) {\n Node e;\n if ((e = oldTab[j]) != null) {\n oldTab[j] = null;\n if (e.next == null)\n // 如果e是該bucket唯一的一個元素,則直接賦值到新數組中\n newTab[e.hash & (newCap - 1)] = e;\n else if (e instanceof TreeNode)\n // split方法會將樹分割爲lower 和upper tree兩個樹,如果子樹的節點數小於了UNTREEIFY_THRESHOLD閾值,則將樹untreeify,將節點都存放在newTab中。\n // TreeNode的情況則使用TreeNode中的split方法將這個樹分成兩個小樹\n ((TreeNode)e).split(this, newTab, j, oldCap);\n else { // preserve order 保持順序\n // 否則則創建兩個鏈表用來存放要放的數據,hash值&oldCap爲0的(即oldCap的1的位置的和hash值的同樣的位置都是1,同樣是基於capacity是2的次方這一前提)爲low鏈表,反之爲high鏈表, 通過這種方式將舊的數據分到兩個鏈表中再放到各自對應餘數的位置\n Node loHead = null, loTail = null;\n Node hiHead = null, hiTail = null;\n Node next;\n do {\n next = e.next;\n // 按照e.hash值區分放在loTail後還是hiTail後\n if ((e.hash & oldCap) == 0) {\n // 運算結果爲0的元素,用lo記錄並連接成新的鏈表\n if (loTail == null)\n loHead = e;\n else\n loTail.next = e;\n loTail = e;\n }\n else {\n // 運算結果不爲0的數據,用li記錄\n if (hiTail == null)\n hiHead = e;\n else\n hiTail.next = e;\n hiTail = e;\n }\n } while ((e = next) != null);\n // 處理完之後放到新數組中\n if (loTail != null) {\n loTail.next = null;\n // lo仍然放在“原處”,這個“原處”是根據新的hash值算出來的\n newTab[j] = loHead;\n }\n if (hiTail != null) {\n hiTail.next = null;\n // li放在j+oldCap位置\n newTab[j + oldCap] = hiHead;\n }\n }\n }\n }\n }\n return newTab;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲取元素","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"get(key) 方法時獲取 key 的 hash 值,計算 hash & (n-1) 得到在鏈表數組中的位置 first=tab[hash&(n-1)],先判斷first的key是否與參數 key相等,不等就遍歷後面的鏈表找到相同的key值返回對應的Value值即可。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public V get(Object key) {\n Node e;\n return (e = getNode(hash(key), key)) == null ? null : e.value;\n }\n\n /**\n * Implements Map.get and related methods\n * @param hash hash for key\n * @param key the key\n * @return the node, or null if none\n */\n final Node getNode(int hash, Object key) {\n Node[] tab; Node first, e; int n; K k;\n if ((tab = table) != null && (n = tab.length) > 0 &&\n (first = tab[(n - 1) & hash]) != null) {\n //如果是頭結點,則直接返回頭結點\n if (first.hash == hash && // always check first node\n ((k = first.key) == key || (key != null && key.equals(k))))\n return first;\n if ((e = first.next) != null) {\n //判斷是否是紅黑樹結構\n if (first instanceof TreeNode)\n //如果是紅黑樹,那就去紅黑樹中找,然後返回\n return ((TreeNode)first).getTreeNode(hash, key);\n do {\n //否則就是鏈表節點,遍歷鏈表,找到該節點並返回\n if (e.hash == hash &&\n ((k = e.key) == key || (key != null && key.equals(k))))\n return e;\n } while ((e = e.next) != null);\n }\n }\n return null;\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"紅黑樹結構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static final class TreeNode extends LinkedHashMap.Entry {\n TreeNode parent; // red-black tree links 父節點\n TreeNode left; // 左子樹\n TreeNode right; // 右子樹\n TreeNode prev; // needed to unlink next upon deletion\n boolean red; // 顏色屬性\n TreeNode(int hash, K key, V val, Node next) {\n super(hash, key, val, next);\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"樹形化操作","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/81/8137475340e7a33a12cba0f2cb55007f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據哈希表中元素個數確定是擴容還是樹形化,如果是樹形化遍歷桶中的元素,創建相同個數的樹形節點,複製內容,建立起聯繫,然後讓桶第一個元素指向新建的樹頭結點,替換桶的鏈表內容爲樹形內容// MIN_TREEIFY_CAPACITY 的值爲64,若當前table的length不夠,則resize() // 將桶內所有的 鏈表節點 替換成 紅黑樹節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"final void treeifyBin(Node[] tab, int hash) {\n int n, index; Node e;\n // 如果當前哈希表爲空,或者哈希表中元素的個數小於樹形化閾值(默認爲 64),就去新建(擴容)\n if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)\n resize();\n // 如果哈希表中的元素個數超過了樹形化閾值,則進行樹形化\n // e 是哈希表中指定位置桶裏的鏈表節點,從第一個開始\n else if ((e = tab[index = (n - 1) & hash]) != null) {\n // 紅黑樹的頭、尾節點\n TreeNode hd = null, tl = null;\n do {\n // 新建一個樹形節點,內容和當前鏈表節點 e 一致\n TreeNode p = replacementTreeNode(e, null);\n // 確定樹頭節點\n if (tl == null)\n hd = p;\n else {\n p.prev = tl;\n tl.next = p;\n }\n tl = p;\n } while ((e = e.next) != null);\n // 讓桶的第一個元素指向新建的紅黑樹頭結點,以後這個桶裏的元素就是紅黑樹而不是鏈表了\n if ((tab[index] = hd) != null)\n hd.treeify(tab);\n }\n}\nTreeNode replacementTreeNode(Node p, \n Node next){\n return new TreeNode<>(p.hash, p.key, p.value, next);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"刪除元素","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"下面再來看看刪除方法remove。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public V remove(Object key) {\n //臨時變量\n Node e;\n /**調用removeNode(hash(key), key, null, false, true)進行刪除,第三個value爲null,表示,把key的節點直接都刪除了,不需要用到值,如果設爲值,則還需要去進行查找操作**/\n return (e = removeNode(hash(key), key, null, false, true)) == null ?\n null : e.value;\n }\n \n /**第一參數爲哈希值,第二個爲key,第三個value,第四個爲是爲true的話,則表示刪除它key對應的value,不刪除key,第四個如果爲false,則表示刪除後,不移動節點**/\n final Node removeNode(int hash, Object key, Object value,\n boolean matchValue, boolean movable) {\n //tab 哈希數組,p 數組下標的節點,n 長度,index 當前數組下標\n Node[] tab; Node p; int n, index;\n //哈希數組不爲null,且長度大於0,然後獲得到要刪除key的節點所在是數組下標位置\n if ((tab = table) != null && (n = tab.length) > 0 &&\n (p = tab[index = (n - 1) & hash]) != null) {\n //nodee 存儲要刪除的節點,e 臨時變量,k 當前節點的key,v 當前節點的value\n Node node = null, e; K k; V v;\n //如果數組下標的節點正好是要刪除的節點,把值賦給臨時變量node\n if (p.hash == hash &&\n ((k = p.key) == key || (key != null && key.equals(k))))\n node = p;\n //也就是要刪除的節點,在鏈表或者紅黑樹上,先判斷是否爲紅黑樹的節點\n else if ((e = p.next) != null) {\n if (p instanceof TreeNode)\n //遍歷紅黑樹,找到該節點並返回\n node = ((TreeNode)p).getTreeNode(hash, key);\n else { //表示爲鏈表節點,一樣的遍歷找到該節點\n do {\n if (e.hash == hash &&\n ((k = e.key) == key ||\n (key != null && key.equals(k)))) {\n node = e;\n break;\n }\n /**注意,如果進入了鏈表中的遍歷,那麼此處的p不再是數組下標的節點,而是要刪除結點的上一個結點**/\n p = e;\n } while ((e = e.next) != null);\n }\n }\n //找到要刪除的節點後,判斷!matchValue,我們正常的remove刪除,!matchValue都爲true\n if (node != null && (!matchValue || (v = node.value) == value ||\n (value != null && value.equals(v)))) {\n //如果刪除的節點是紅黑樹結構,則去紅黑樹中刪除\n if (node instanceof TreeNode)\n ((TreeNode)node).removeTreeNode(this, tab, movable);\n //如果是鏈表結構,且刪除的節點爲數組下標節點,也就是頭結點,直接讓下一個作爲頭\n else if (node == p)\n tab[index] = node.next;\n else /**爲鏈表結構,刪除的節點在鏈表中,把要刪除的下一個結點設爲上一個結點的下一個節點**/\n p.next = node.next;\n //修改計數器\n ++modCount;\n //長度減一\n --size;\n /**此方法在hashMap中是爲了讓子類去實現,主要是對刪除結點後的鏈表關係進行處理**/\n afterNodeRemoval(node);\n //返回刪除的節點\n return node;\n }\n }\n //返回null則表示沒有該節點,刪除失敗\n return null;\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"刪除還有clear方法,把所有的數組下標元素都置位null。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"size()方法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"HashMap 的大小很簡單,不是實時計算的,而是每次新增加 Entry 的時候,size 就遞增。刪除的時候就遞減。空間換時間的做法。因爲它不是線程安全的。完全可以這麼做,效率高","attrs":{}},{"type":"text","text":"。","attrs":{}}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章