jdk 源碼系列之HashMap

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"jdk 源碼系列之 HashMap"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"JDK 源碼系列"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#jdk-%E6%BA%90%E7%A0%81%E7%B3%BB%E5%88%97","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/09/29/jdk-StringBuilder-StringBuffer/","title":null},"content":[{"type":"text","text":"jdk 源碼系列之StringBuilder、StringBuffer"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"前言"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#%E5%89%8D%E8%A8%80","title":null}}]},{"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,理解擴容機制、數據結構、閾值以及初始化機制,對使用、優化等有所裨益。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"繼承關係"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#%E7%BB%A7%E6%89%BF%E5%85%B3%E7%B3%BB","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"1\n2\n3\npublic class HashMap extends AbstractMap\n implements Map, Cloneable, Serializable {\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"從這段代碼,我們可知,HashMap"},{"type":"text","text":" 繼承 "},{"type":"text","marks":[{"type":"strong"}],"text":"AbstractMap"},{"type":"text","text":" 同時實現 "},{"type":"text","marks":[{"type":"strong"}],"text":"Map"},{"type":"text","text":" 類。換句話說,"},{"type":"text","marks":[{"type":"strong"}],"text":"Map"},{"type":"text","text":" 作爲頂級父類,只定義方法,實現由子類實現,還繼承 "},{"type":"text","marks":[{"type":"strong"}],"text":"AbstractMap"},{"type":"text","text":" 那也是說,也可以調用 "},{"type":"text","marks":[{"type":"strong"}],"text":"AbstractMap"},{"type":"text","text":" 裏面的方法。"}]},{"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":"這裏先摁住,還不清楚 "},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 調了 "},{"type":"text","marks":[{"type":"strong"}],"text":"AbstractMap"},{"type":"text","text":" 裏面哪些方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"HashMap"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#hashmap","title":null}}]},{"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":"點進去先看看是數據結構是什麼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"數據結構"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n/**\n * Basic hash bin node, used for most entries. (See below for\n * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)\n */\nstatic 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\n public final K getKey() { return key; }\n public final V getValue() { return value; }\n public final String toString() { return key + \"=\" + value; }\n\n public final int hashCode() {\n return Objects.hashCode(key) ^ Objects.hashCode(value);\n }\n\n public final V setValue(V newValue) {\n V oldValue = value;\n value = newValue;\n return oldValue;\n }\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"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"向上繼承 "},{"type":"text","marks":[{"type":"strong"}],"text":"Map.Entry"},{"type":"text","text":" 接口,並實現了 "},{"type":"text","marks":[{"type":"strong"}],"text":"getKey"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"getValue"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"toString"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"hashCode"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"setValue"},{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"equals"},{"type":"text","text":" 這六個方法。之後自己在實現一個鏈表 "},{"type":"text","marks":[{"type":"strong"}],"text":"Node"},{"type":"text","text":" 的內部類,這個鏈表的節點都保存三個東西,hash key value 這三個變量。"}]},{"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":"接下來,我們從創建一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 入手。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"創建 HashMap"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#%E5%88%9B%E5%BB%BA-hashmap","title":null}}]},{"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":"通常來講,"},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 有三種 new 的方式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\nMap first = new HashMap<>();\n\nMap second = new HashMap<>(12);\n\nMap third = new HashMap<>(first);\n\nMap fourth = new HashMap<>(12 , 1);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一種是無參構造,第二種是傳入一個 int 值,第三種是允許傳入一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"Map"},{"type":"text","text":" 實現的任何子類,第四種是兩個 int 類型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n/**\n * Constructs an empty HashMap with the default initial capacity\n * (16) and the default load factor (0.75).\n */\n// first 的構造\npublic HashMap() {\n this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted\n}\n\n/**\n * Constructs an empty HashMap with the specified initial\n * capacity and the default load factor (0.75).\n *\n * @param initialCapacity the initial capacity.\n * @throws IllegalArgumentException if the initial capacity is negative.\n */\n// second 的構造\npublic HashMap(int initialCapacity) {\n this(initialCapacity, DEFAULT_LOAD_FACTOR);\n}\n\n/**\n * Constructs a new HashMap with the same mappings as the\n * specified Map. The HashMap is created with\n * default load factor (0.75) and an initial capacity sufficient to\n * hold the mappings in the specified Map.\n *\n * @param m the map whose mappings are to be placed in this map\n * @throws NullPointerException if the specified map is null\n */\n// third 的構造\npublic HashMap(Map extends K, ? extends V> m) {\n this.loadFactor = DEFAULT_LOAD_FACTOR;\n putMapEntries(m, false);\n}\n\n/**\n * Constructs an empty HashMap with the specified initial\n * capacity and load factor.\n *\n * @param initialCapacity the initial capacity\n * @param loadFactor the load factor\n * @throws IllegalArgumentException if the initial capacity is negative\n * or the load factor is nonpositive\n */\n// fourth 的構造\npublic 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"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏提到三個變量,分別 "},{"type":"text","marks":[{"type":"strong"}],"text":"initialCapacity"},{"type":"text","text":"、"},{"type":"text","marks":[{"type":"strong"}],"text":"loadFactor"},{"type":"text","text":"、"},{"type":"text","marks":[{"type":"strong"}],"text":"threshold"},{"type":"text","text":"。"}]},{"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":"看中文意思,分別是初始化容量、負載因子、閾值。其中負載因子,也就是 "},{"type":"text","marks":[{"type":"strong"}],"text":"loadFactor"},{"type":"text","text":" 默認是 0.75f"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n/**\n * The load factor used when none specified in constructor.\n */\nstatic final float DEFAULT_LOAD_FACTOR = 0.75f;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"text","marks":[{"type":"strong"}],"text":"third"},{"type":"text","text":" 這個構造函數裏面,有個 "},{"type":"text","marks":[{"type":"strong"}],"text":"putMapEntries"},{"type":"text","text":" 的方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n /**\n * Implements Map.putAll and Map constructor.\n * 構造方法的時候就全部添加進去 hashMap\n * @param m the map 傳入的Map\n * @param evict false when initially constructing this map, else\n * true (relayed to method afterNodeInsertion). 構造使用到就是 false 非構造就是 true\n */\n final void putMapEntries(Map extends K, ? extends V> m, boolean evict) {\n // Map 的大小\n int s = m.size();\n if (s > 0) {\n // 由於是構造調用的,table = null\n if (table == null) { // pre-size\n // 在調用次方法的時候,先賦值了 loadFactor = 0.75。所以ft = (Map 長度 / 0.75) + 1.0\n float ft = ((float)s / loadFactor) + 1.0F;\n // 判斷是否超出 MAXIMUM_CAPACITY = 1 << 30 也就是 2 的30次冪大小\n int t = ((ft < (float)MAXIMUM_CAPACITY) ?\n (int)ft : MAXIMUM_CAPACITY);\n // 起始 threshold = 0,這個也在 fourth 的構造裏面使用到了,下面有解釋\n if (t > threshold)\n // 如果 t 的大小在 2 的(n-1)次冪 與 2 的 n 次冪之間,則 t 的大小爲 2 的n次冪。不懂沒關係,下面有解釋、舉例\n threshold = tableSizeFor(t);\n }\n // Map的長度 是否 大於 剛剛 tableSizeFor 之後的長度\n // 首次是不會大於這個值的\n else if (s > threshold)\n // 擴容,後面對這個方法講解\n resize();\n \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 // 先通過位運算減少 key 的衝突,在放進去\n putVal(hash(key), key, value, false, evict);\n }\n }\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"putMapEntries"},{"type":"text","text":" 這個方法就是 HashMap 在構造的時候,放進 "},{"type":"text","marks":[{"type":"strong"}],"text":"Map"},{"type":"text","text":" 的對象,那麼就會將他是所有 k v 都放進去,然後初始化長度,這個長度是當前 Map 長度靠近 2 n次冪 長度。比如"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Map 7,則HashMap 初始化8,"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是12 則是 16,"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是16 則是 16."}]}]}]},{"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。先通過位運算計算 Hash 減少衝突,在pull。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n /**\n * Computes key.hashCode() and spreads (XORs) higher bits of hash\n * to lower. Because the table uses power-of-two masking, sets of\n * hashes that vary only in bits above the current mask will\n * always collide. (Among known examples are sets of Float keys\n * holding consecutive whole numbers in small tables.) So we\n * apply a transform that spreads the impact of higher bits\n * downward. There is a tradeoff between speed, utility, and\n * quality of bit-spreading. Because many common sets of hashes\n * are already reasonably distributed (so don't benefit from\n * spreading), and because we use trees to handle large sets of\n * collisions in bins, we just XOR some shifted bits in the\n * cheapest possible way to reduce systematic lossage, as well as\n * to incorporate impact of the highest bits that would otherwise\n * never be used in index calculations because of table bounds.\n *\n * 通過位運算減少 Hash 衝突\n */\n static final int hash(Object key) {\n int h;\n return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"text","marks":[{"type":"strong"}],"text":"fourth"},{"type":"text","text":" 這個構造函數裏面,還有一個這 "},{"type":"text","marks":[{"type":"strong"}],"text":"tableSizeFor"},{"type":"text","text":" 的方法,上面有用到。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\nstatic final int tableSizeFor(int cap) {\n int n = cap - 1;\n n |= n >>> 1;\n n |= n >>> 2;\n n |= n >>> 4;\n n |= n >>> 8;\n n |= n >>> 16;\n return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進行了位運算,由於前面好保證了 cap 非負數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"»>"},{"type":"text","text":" 右移,高位補零。比如 0001,使用 »> 1 之後就是 00001,對其長度,則1去掉。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"**** 或操作,不同取1,相同1取1,相同0取0"}]}]}]},{"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"}],"text":"tableSizeFor"},{"type":"text","text":" 方法裏面的意思就是,如果 cap 介於 "},{"type":"text","marks":[{"type":"strong"}],"text":"2 (n - 1)次冪"},{"type":"text","text":" 與"},{"type":"text","marks":[{"type":"strong"}],"text":"2 n次冪"},{"type":"text","text":"之間,cap 的值爲 "},{"type":"text","marks":[{"type":"strong"}],"text":"2 n次冪 - 1"},{"type":"text","text":" 大小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\nint n = 18;\nn |= n >>> 1;\nn |= n >>> 2;\nn |= n >>> 4;\nn |= n >>> 8;\nn |= n >>> 16;\nSystem.err.println(n); // 31\n\nint n = 16;\nn |= n >>> 1;\nn |= n >>> 2;\nn |= n >>> 4;\nn |= n >>> 8;\nn |= n >>> 16;\nSystem.err.println(n); // 31\n\nint n = 14;\nn |= n >>> 1;\nn |= n >>> 2;\nn |= n >>> 4;\nn |= n >>> 8;\nn |= n >>> 16;\nSystem.err.println(n); // 15\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後只要算的 n 非負數,且不大於 int 類型最大值,則 n + 1。也就是說這個初始容量,無論設置什麼數字,都會是 2 的倍數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"HashMap 添加"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#hashmap-%E6%B7%BB%E5%8A%A0","title":null}}]},{"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":"我們常常使用 kv 的形式存儲。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n map.put(\"sin\", \"sy\");\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點進去看看,"},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 如何存儲。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n // 首先來到父類定義的\n V put(K key, V value);\n\n\n /**\n * Associates the specified value with the specified key in this map.\n * If the map previously contained a mapping for the key, the old\n * value is replaced.\n *\n * @param key key with which the specified value is to be associated\n * @param value value to be associated with the specified key\n * @return the previous value associated with key, or\n * null if there was no mapping for key.\n * (A null return can also indicate that the map\n * previously associated null with key.)\n * hashMap 的添加\n */\n // 然後實現父類定義的方法\n public V put(K key, V value) {\n // hash 方法上面有提到作用\n return putVal(hash(key), key, value, false, true);\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"pull value 的時候,傳入先經過位運算的 key 的 hashCode,然後傳 key value、false,true"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\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 這個值在pull 的時候是false\n * @param evict if false, the table is in creation mode. 這個值在pull 的時候是 true\n * @return previous value, or null if none\n */\n final V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n boolean evict) {\n // 初始化兩個容器 一個 k v 數組鏈表、一個是鏈表\n Node[] tab; Node p; int n, i;\n // 第一次這裏是null,所以 n = (tab = resize()).length;\n if ((tab = table) == null || (n = tab.length) == 0)\n // resize 下面有講解\n // 是否擴容處理,沒有擴容,則是當前長度,有則是之前的兩倍\n n = (tab = resize()).length;\n // 因爲數組從0開始計算,所以是 n - 1 取模,確定值放置在數值鏈表位置\n // 如果當前數組鏈表爲null,同時 p = 鏈表table\n if ((p = tab[i = (n - 1) & hash]) == null)\n // 創建一個鏈表\n // 沒有key值,就給你創建一個\n tab[i] = newNode(hash, key, value, null);\n // 有值的情況下\n else {\n // 數組鏈表頭有數據\n // 初始化 鏈表 泛型k\n Node e; K k;\n // hash 值一致且key值的地址也一樣 或者 key不爲null 且key的值相同\n // 大概就是相同key,替換value\n if (p.hash == hash &&\n ((k = p.key) == key || (key != null && key.equals(k))))\n e = p;\n // 如果 p 是 樹結構\n else if (p instanceof TreeNode)\n // 按樹的形式 pul 進去\n e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);\n // 值不相同,也不是樹結構,且鏈表頭還相同,同一節點底下還有鏈表\n else {\n // 向下遍歷\n for (int binCount = 0; ; ++binCount) {\n // 鏈表下一節點爲null\n if ((e = p.next) == null) {\n // p的下一節點指向創建一個新的鏈表\n p.next = newNode(hash, key, value, null);\n // 當鏈表的長度大於等於7,因爲從1開始,也是說鏈表頭有值,所以才從一開始。另外這是 ++1 性能比 i++好,i++比起++i多了一個創建臨時變量(放入數據棧)的步驟,因此效率低一些\n if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st\n // 鏈表轉樹結構\n treeifyBin(tab, hash);\n break;\n }\n // key 值相同\n if (e.hash == hash &&\n ((k = e.key) == key || (key != null && key.equals(k))))\n break;\n p = e;\n }\n }\n // 當鏈表不爲null,建立映射,kv\n if (e != null) { // existing mapping for key\n V oldValue = e.value;\n // onlyIfAbsent 如果是 true 不改變現有的值,false 改變,或者 oldValue == null\n if (!onlyIfAbsent || oldValue == null)\n // 完成關聯\n e.value = value;\n // 尾插,鏈表直接插到當前鏈表的最後一個位置上\n afterNodeAccess(e);\n return oldValue;\n }\n }\n // 快速失敗機制,比較當前操作次數與記錄次數,是否不一樣\n ++modCount;\n // 因爲完成一個 pulValue 所以增加一次次數,同時再次比較是否來到閾值\n if (++size > threshold)\n // 來到閾值,2倍擴容容器\n resize();\n // true 如果是頭結點則刪除\n afterNodeInsertion(evict);\n return null;\n }\n\n\n /**\n * Initializes or doubles table size. If null, allocates in\n * accord with initial capacity target held in field threshold.\n * Otherwise, because we are using power-of-two expansion, the\n * elements from each bin must either stay at same index, or move\n * with a power of two offset in the new table.\n * 具有初始化容器大小、2倍擴容容器等功能\n * @return the table\n */\n final Node[] resize() {\n // 首次 table 是null\n Node[] oldTab = table;\n // 判斷 oldCap 是否爲 null 爲null 則爲0,反之則是容器長度\n int oldCap = (oldTab == null) ? 0 : oldTab.length;\n // 閾值 首次是0\n int oldThr = threshold;\n // 新容器容量0 新容器閾值0\n int newCap, newThr = 0;\n // 容量大於0\n if (oldCap > 0) {\n // 大於等於最大能容納的長度\n if (oldCap >= MAXIMUM_CAPACITY) {\n // 閾值等於最大能容納的長度\n threshold = Integer.MAX_VALUE;\n return oldTab;\n }\n // 容器長度是否大於默認16,且新容器是舊容器的兩倍大小且不大於最大容納長度,\n else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&\n oldCap >= DEFAULT_INITIAL_CAPACITY)\n // 新閾值也是舊閾值的兩倍大小\n newThr = oldThr << 1; // double threshold\n }\n // 舊閾值 大於 0\n else if (oldThr > 0) // initial capacity was placed in threshold\n // 新容器長度 = 舊閾值\n newCap = oldThr;\n else { // zero initial threshold signifies using defaults\n // 首次 pull的時候\n // 新容器長度 = 16\n newCap = DEFAULT_INITIAL_CAPACITY;\n // 新閾值等於 負載因子 0.75 * 默認長度 16 = 12\n newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);\n }\n // 這個判斷只有在第一次使用構造方法傳入一個 Map 纔會用到,且這個Map 有值,其他是都基本走不到這裏\n if (newThr == 0) {\n // 容器長度 * 負載因子 0.75\n float ft = (float)newCap * loadFactor;\n // 只要不大於最大容納的長度,則 newThr = ft\n newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?\n (int)ft : Integer.MAX_VALUE);\n }\n // 賦值到類變量裏面,基本確定閾值\n threshold = newThr;\n @SuppressWarnings({\"rawtypes\",\"unchecked\"})\n // 新數組鏈表 等於 新容器的長度\n Node[] newTab = (Node[])new Node[newCap];\n // 賦值到類的變量裏面\n table = newTab;\n // 舊容器不爲null\n // 這是是首次 oldTab 爲 null 不走這裏,比如第一次 pull 構造方法放入map的子類\n // 之後 pull 這裏都會走一遍\n if (oldTab != null) {\n // 鏈表循環指向下一個節點\n for (int j = 0; j < oldCap; ++j) {\n Node e;\n // 先賦值,當前數組鏈表的鏈表給 e 鏈表且不爲null\n if ((e = oldTab[j]) != null) {\n // 設置數組鏈表爲null\n oldTab[j] = null;\n // 鏈表的下一節點爲null\n if (e.next == null)\n // 只有數組鏈表頭有數據,子節點沒有數據\n // hash 取模,這個模的長度是數組的長度,e 接到新的容器底下\n newTab[e.hash & (newCap - 1)] = e;\n // 判斷 e 是否是樹結構\n else if (e instanceof TreeNode)\n ((TreeNode)e).split(this, newTab, j, oldCap);\n // e 不是樹結構,且數組鏈表的子節點也有數據\n // 這裏應該是計算不同hash的節點,到不同鏈表上\n // 然後遷移到新的容器裏,對應的數組鏈表的位置\n else { // preserve order\n // \n Node loHead = null, loTail = null;\n Node hiHead = null, hiTail = null;\n // 下一節點\n Node next;\n do {\n // e 指向下一節點\n next = e.next;\n // e 的hash 取模 oldCap,分散。\n if ((e.hash & oldCap) == 0) {\n // 第一次爲null\n if (loTail == null)\n // loHead 是第一值\n loHead = e;\n else\n // loTail的下一節點 指向 e的下一節點\n loTail.next = e;\n // 不是最後一個位置\n loTail = e;\n }\n // \n else {\n // 和上面的步驟差不多\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 newTab[j] = loHead;\n }\n // 2倍擴容之後的數組長度,換句話說,之前的數據,如果 hash 取模之後不等於,之前最後面的位置,則一律變成之前的位置 加上 舊數組長度的。\n // 如果之前是 2 數組鏈表長度是 8,之後這個位置來到 10\n if (hiTail != null) {\n hiTail.next = null;\n newTab[j + oldCap] = hiHead;\n }\n }\n }\n }\n }\n return newTab;\n }\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結下,在 pulValue 的時候做了什麼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"先初始化數據結構,數組鏈表"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"如果沒有設置初始化大小,則默認長度爲16,同時設置閾值爲 0.75 * 當前長度"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果長度來到閾值大小,則進行當前容量二倍擴容,數據大部分會移到新的長度鏈表裏面,少部分會遺留在舊容器長度裏面"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"查看插入 key 是否存在,有則尾插入,插入之後是否來到 8 ,鏈表長度來到8則開始轉成紅黑樹。沒有,直接插入"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"然後 key 的位置裏,在放入 value。完成關聯"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"記錄操作次數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"插入後,在繼續判斷是否擴容"}]}]}]},{"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":"上面,少了鏈表如何轉紅黑樹。接下來,我們繼續看看"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n /**\n * Replaces all linked nodes in bin at index for given hash unless\n * table is too small, in which case resizes instead.\n */\n final void treeifyBin(Node[] tab, int hash) {\n int n, index; Node e;\n // 鏈表長度是否小於 64,換句話說,樹長最長只有64\n if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)\n // 當鏈表長度,來到 8 的時候也會判斷是否擴容\n resize();\n // 確定位置\n else if ((e = tab[index = (n - 1) & hash]) != null) {\n TreeNode hd = null, tl = null;\n do {\n // 鏈表一個一個替換成樹節點\n TreeNode p = replacementTreeNode(e, null);\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 if ((tab[index] = hd) != null)\n // 樹節點,在轉成紅黑樹,可以認爲樹節點拼接成一個樹\n hd.treeify(tab);\n }\n }\n\n\n /**\n * Forms tree of the nodes linked from this node.\n */\n final void treeify(Node[] tab) {\n TreeNode root = null;\n for (TreeNode x = this, next; x != null; x = next) {\n next = (TreeNode)x.next;\n x.left = x.right = null;\n // 起始 red = false,當 = false 時候就是 黑色,同時根節放入值,只有首次纔會走到\n if (root == null) {\n x.parent = null;\n x.red = false;\n root = x;\n }\n else {\n K k = x.key;\n int h = x.hash;\n Class> kc = null;\n // 遍歷鏈表\n for (TreeNode p = root;;) {\n int dir, ph;\n K pk = p.key;\n if ((ph = p.hash) > h)\n dir = -1;\n else if (ph < h)\n dir = 1;\n else if ((kc == null &&\n (kc = comparableClassFor(k)) == null) ||\n (dir = compareComparables(kc, k, pk)) == 0)\n dir = tieBreakOrder(k, pk);\n\n TreeNode xp = p;\n if ((p = (dir <= 0) ? p.left : p.right) == null) {\n x.parent = xp;\n if (dir <= 0)\n xp.left = x;\n else\n xp.right = x;\n // 這裏通過左旋 或者右旋,使得 黑的節點變成父節點,自旋完成後,父節點爲黑色,而子節點都是紅色\n root = balanceInsertion(root, x);\n break;\n }\n }\n }\n }\n moveRootToFront(tab, root);\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉紅黑樹的過程中,首先會進行一次判斷擴容處理,之後先變成一個個的樹節點,在變成紅黑樹。"}]},{"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":"其中 red = false 時,爲黑,反之則是紅。通過左右旋,使其父節點變成黑色,子節點爲紅色,完成一次平衡處理。"}]},{"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":"圖如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ad/ad859eac23be068ea77af6cd91d52a1c.gif","alt":null,"title":null,"style":null,"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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首次,root節點就是隻有一個,所以直接是黑。噹噹前樹高相差爲3,會完成一次自旋,使其平衡,如果是不平衡的節點,則會變成紅色,平衡的節點則爲黑色。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"HashMap 的數據結構圖解"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#hashmap-%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%9B%BE%E8%A7%A3","title":null}}]},{"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是怎麼擴容的,如何鏈表轉紅黑樹、如何定位位置。接下來我們看看HashMap的數據結構是如何。"}]},{"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":"起始的結構是由數組鏈表裏面且是一個kv結構。頭部是一個數組,k的位置是一個 hash 取模(位擾動之後的hash)之後,確定的位置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/73/73a372ed0c21e103d4eb1f813c750ba5.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後來當,hash衝突變多的時候,數組底下的鏈表就是存儲 hash 衝突之後存儲的位置,這個長度大於8的時候轉紅黑樹,包括頭,也就是數組,底下的鏈表長度是7。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d8/d8e8db22ab2b3435dec152a6d03134d3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"優化以及使用 HashMap 的建議"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#%E4%BC%98%E5%8C%96%E4%BB%A5%E5%8F%8A%E4%BD%BF%E7%94%A8-hashmap-%E7%9A%84%E5%BB%BA%E8%AE%AE","title":null}}]},{"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":"既然讀源碼,就是爲了更好的瞭解這個東西,只爲讀而讀,爲面試而讀,這個源碼讀的沒什麼意思。學完得要有自己體會吧~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"當使用的 "},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 是固定不變或者未達到 2 的n次方長度,比如只使用到3,而當前 HashMap 長度爲4,可以直接設置負載因子,固定其長度,避免過早的擴容,而浪費空間。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 默認初始化大小爲16,如果添加的值,不到16的話,甚至只有它的一半,這樣很容易造成空間上的浪費,之後過多的空間浪費,從而觸發 GC 回收,降低整個系統的響應時間。"},{"type":"text","marks":[{"type":"strong"}],"text":"所以儘可能的初始化大小,避免空間的浪費"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"當使用的 "},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 長度超過16,都是沒有初始化容器大小,來到閾值大小,頻繁的觸發擴容機制,導致 "},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 性能下降。"},{"type":"text","marks":[{"type":"strong"}],"text":"所以也儘可能的初始化大小,避免頻繁觸發擴容機制。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 線程不安全,這裏主要是這兩點(JDK8)通過源碼我們知道,在添加元素的時候,在添加前以及添加後都會判斷是否擴容,而改變 kv 的分佈。在多線程中,如果 A 線程先添加完值,改變了整個容器值的分佈,而 B 線程拿到的是原先容器大小來判斷位置,從而導致插入的值不在正確的位置上。"},{"type":"text","marks":[{"type":"strong"}],"text":"HashMap"},{"type":"text","text":" 裏面有記錄操作次數,在多線程中,當前的操作次數,與記錄操作次數,可能會不正確,(可能拿到舊值)而出發快速失敗機制。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"只要是繼承了 "},{"type":"text","marks":[{"type":"strong"}],"text":"Map"},{"type":"text","text":" 接口的子類,都可以轉成 HashMap,在創建對象的時候。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"聲明"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#%E5%A3%B0%E6%98%8E","title":null}}]},{"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":"作者: Sinsy"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文鏈接:https://blog.sincehub.cn/2020/10/31/jdk-hashmap/"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"版權聲明:本文爲博主原創文章,遵循 "},{"type":"link","attrs":{"href":"https://creativecommons.org/licenses/by-sa/4.0/deed.zh","title":null},"content":[{"type":"text","text":"CC 4.0 BY-SA "}]},{"type":"text","text":"版權協議,轉載請附上原文聲明。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如您有任何商業合作或者授權方面的協商,請給我留言:[email protected]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"引用"},{"type":"link","attrs":{"href":"https://blog.sincehub.cn/2020/10/31/jdk-HashMap/#%E5%BC%95%E7%94%A8","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[1] "},{"type":"link","attrs":{"href":"https://www.cs.usfca.edu/~galles/visualization/Algorithms.html","title":null},"content":[{"type":"text","text":"Data Structure Visualizations"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章