摘要:源碼分析版本聲明文章均為本人技術筆記,轉載請注明出處聲明結構圖示基本數據結構本質是一個散列表,存儲元素為鍵值對繼承,實現了接口的是線程不安全的,它的都可以為默認裝填因子,如果當前鍵值對個數最大容量裝填因子,進行操作新加,鏈表最大長度,當桶中
HashMap源碼分析_JDK1.8版本 聲明
文章均為本人技術筆記,轉載請注明出處
[1] https://segmentfault.com/u/yzwall
[2] blog.csdn.net/j_dark/
HashMap結構圖示 HashMap基本數據結構public class HashMap
extends AbstractMap implements Map , Cloneable, Serializable
HashMap本質是一個散列表,存儲元素為鍵值對;
HashMap繼承AbstractMap,實現了Map、Cloneable、java.io.Serializable接口;
HashMap的是線程不安全的,它的key、value都可以為null;
final int loadFacotr
static final float DEFAULT_LOAD_FACTOR: 默認裝填因子0.75,如果當前鍵值對個數 >= HashMap最大容量*裝填因子,進行rehash操作;
int threshold
static final int TREEIFY_THRESHOLD: JDK1.8 新加,Entry鏈表最大長度,當桶中節點數目大于該長度時,將鏈表轉成紅黑樹存儲;
static final int UNTREEIFY_THRESHOLD:JDK1.8 新加,當桶中節點數小于該長度,將紅黑樹轉為鏈表存儲;
static final int DEFAULT_INITIAL_CAPACITY: 默認鍵值隊個數為16;
transient Node
JDK1.6用Entry描述鍵值對,JDK1.8中用Node代替Entry;
static class Nodeimplements Map.Entry { // hash存儲key的hashCode final int hash; // final:一個鍵值對的key不可改變 final K key; V value; // 一個桶中的鍵值對用鏈表組織 Node next; ... }
HashMap中鍵值對的存儲形式為鏈表節點,hashCode相同的節點(位于同一個桶)用鏈表組織;
public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); }
鍵值對元素hashCode = key的hashCode與value的hashCode,高16位與低16位按位異或運算;
紅黑樹:TreeNodestatic final class TreeNode
JDK1.8新增,用來支持桶的紅黑樹結構實現
HashMap重要方法分析 HashMap添加/更新鍵值對:put/putVal方法public V put(K key, V value)內部調用putVal方法實現;
public V put(K key, V value) { // 倒數第二個參數false:表示允許舊值替換 // 最后一個參數true:表示HashMap不處于創建模式 return putVal(hash(key), key, value, false, true); }putVal方法分析:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab; Node p; int n, i; // 槽數組未初始化或者未擴容,先調用resize()擴容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; /** * Hash函數,(n - 1) & hash 計算key將被放置的槽位; * (n - 1) & hash 本質上是hash % n,位運算更快 */ if ((p = tab[i = (n - 1) & hash]) == null) // 空桶,創建新的鍵值對節點,放入槽數組中; tab[i] = newNode(hash, key, value, null); // 鍵值對已在對應桶中 else { Node e; K k; // 與桶中首元素比較,如果key不同發生Hash沖突,在桶中添加新元素 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 當前桶中無該鍵值對,且桶是紅黑樹結構,按照紅黑樹結構插入 else if (p instanceof TreeNode) e = ((TreeNode )p).putTreeVal(this, tab, hash, key, value); // 當前桶中無該鍵值對,且桶是鏈表結構,按照鏈表結構插入到尾部 else { for (int binCount = 0; ; ++binCount) { // 遍歷到鏈表尾部 if ((e = p.next) == null) { // 創建鏈表節點并插入尾部 p.next = newNode(hash, key, value, null); // 檢查鏈表長度是否達到閾值,達到將該槽位節點組織形式轉為紅黑樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 鏈表節點的 與put操作 相同時,不做重復操作,跳出循環 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 找到 or 新建一個key和hashCode與插入元素相等的鍵值對,進行put操作 if (e != null) { // existing mapping for key V oldValue = e.value; /** * onlyIfAbsent為false或舊值為null時,允許替換舊值 * 否則無需替換 */ if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } // 更新結構化修改信息 ++modCount; // 鍵值對數目超過閾值時,進行rehash if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
鍵值對
鍵值對槽位是鍵值對在tab數組的索引,本質上 = hash(key) % 容量,位運算速度更快;
本質上是除數取余法,盡可能的散列均勻;
// in HashMap static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
計算key的hashCode, h = Objects.hashCode(key);
h >>> 16表示對h無符號右移16位,高位補0;然后h與h >>> 16按位異或;
HashMap更新舊鍵值對 or 添加新鍵值對的核心思想:
根據鍵值對key的hashCode計算鍵值對的在HashMap中槽位,
判斷是否空桶 Or 是否發生Hash沖突(與桶中首元素不同)
解決Hash沖突:根據桶組織形式是紅黑樹 Or 鏈表進行對應插入操作;
鏈表形式完成插入后,檢查是否超過鏈表閾值,超過將鏈表->紅黑樹;
最后檢查鍵值對總數是否超過閾值,超過調用resize()進行rehash操作;
HashMap刪除鍵值對:remove/removeNode方法 remove方法分析public V remove(Object key) { Nodee; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; }
remove()方法內部調用removeNode()方法實現
removeNode方法分析:final NodeHashMap訪問鍵值對:get/getNode方法 get方法removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node [] tab; Node p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; // 待刪除元素在桶中,但不是桶中首元素 else if ((e = p.next) != null) { // 待刪除元素在紅黑樹結構的桶中 if (p instanceof TreeNode) // 查找紅黑樹 node = ((TreeNode )p).getTreeNode(hash, key); else { // 遍歷鏈表,查找待刪除元素 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } // p保存待刪除節點的前一個節點,用于鏈表刪除操作 p = e; } while ((e = e.next) != null); } } /** * matchValue為true:表示必須value相等才進行刪除操作 * matchValue為false:表示無須判斷value,直接根據key進行刪除操作 */ if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { // 桶為紅黑數結構,刪除節點 if (node instanceof TreeNode) // movable參數用于紅黑樹操作 ((TreeNode )node).removeTreeNode(this, tab, movable); // 待刪除節點是桶鏈表表頭,將子節點放進桶位 else if (node == p) tab[index] = node.next; // 待刪除節點在桶鏈表中間 else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } // 待刪除元素不存在,返回null return null; }
public V get(Object key) { Nodee; return (e = getNode(hash(key), key)) == null ? null : e.value; }
get方法通過指定key獲得對應鍵值對,內部調用getNode方法實現;
getNode方法final NodegetNode(int hash, Object key) { Node [] tab; Node first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { // 紅黑樹查找 if (first instanceof TreeNode) return ((TreeNode )first).getTreeNode(hash, key); // 鏈表查找 do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
getNode方法查找過程和putVal一樣,先查找對應桶的首元素,然后根據紅黑樹結構 Or 鏈表結構對應查找;
HashMap重散列操作:resize方法final Node[] resize() { // 保存舊table,容量,閾值 Node [] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { // 舊table容量已超過最大容量,更新閾值為Integer.MAX_VALUE if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 調整新容量為舊容量2倍,左移一位實現 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // oldCap == 0 && oldThr > 0 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; // oldCap == 0 && oldThr == 0 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node [] newTab = (Node [])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 紅黑樹桶進行rehash操作 else if (e instanceof TreeNode) ((TreeNode )e).split(this, newTab, j, oldCap); // 鏈表桶進行rehash操作 // 根據e.hash & oldCap)是否為0把鏈表分成兩個鏈表,進行rehash else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
當鍵值對總數超過閾值threshold, HashMap通過resize方法實現重散列/rehash
HashMap調整容量:tableSizeFor()static final int tableSizeFor(int cap):得到>=cap的2的最小冪值;
由指定容量參數的構造器調用,計算rehash閾值threshold;
[1] http://www.cnblogs.com/leesf456/p/5242233.html
[2] http://www.cnblogs.com/ToBeAProgrammer
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/66967.html
摘要:所謂拉鏈法就是將鏈表和數組相結合。若遇到哈希沖突,則將沖突的值加到鏈表中即可。在編寫程序中,要盡量避免。 目錄: 0-1. 簡介 0-2. 內部結構分析 0-2-1. JDK18之前 0-2-2. JDK18之后 0-3. LinkedList源碼分析 0-3-1. 構造方法 0-3-2. put方法 0-3-3. get方法 0-3-4. resize方法 ...
摘要:則使用了拉鏈式的散列算法,并在中引入了紅黑樹優化過長的鏈表。如果大家對紅黑樹感興趣,可以閱讀我的另一篇文章紅黑樹詳細分析。構造方法構造方法分析的構造方法不多,只有四個。 1.概述 本篇文章我們來聊聊大家日常開發中常用的一個集合類 - HashMap。HashMap 最早出現在 JDK 1.2中,底層基于散列算法實現。HashMap 允許 null 鍵和 null 值,在計算哈鍵的哈希值...
摘要:發生了線程不安全情況。本來在中,發生哈希沖突是可以用鏈表法或者紅黑樹來解決的,但是在多線程中,可能就直接給覆蓋了。中,當同一個值上元素的鏈表節點數不小于時,將不再以單鏈表的形式存儲了,會被調整成一顆紅黑樹。 showImg(https://segmentfault.com/img/bVbsVLk?w=288&h=226); List 和 Set 的區別 List , Set 都是繼承自...
摘要:當一個值中要存儲到的時候會根據的值來計算出他的,通過哈希來確認到數組的位置,如果發生哈希碰撞就以鏈表的形式存儲在源碼分析中解釋過,但是這樣如果鏈表過長來的話,會把這個鏈表轉換成紅黑樹來存儲。 正文開始 注:JDK版本為1.8 HashMap1.8和1.8之前的源碼差別很大 目錄 簡介 數據結構 類結構 屬性 構造方法 增加 刪除 修改 總結 1.HashMap簡介 H...
ConcurrentHashMap源碼分析_JDK1.8版本 聲明 文章均為本人技術筆記,轉載請注明出處[1] https://segmentfault.com/u/yzwall[2] blog.csdn.net/j_dark/ JDK1.6版本 ConcurrentHashMap結構 showImg(https://segmentfault.com/img/remote/146000000900...
閱讀 3900·2021-11-18 13:19
閱讀 1179·2021-10-11 10:58
閱讀 3286·2019-08-29 16:39
閱讀 3139·2019-08-26 12:08
閱讀 2034·2019-08-26 11:33
閱讀 2459·2019-08-23 18:30
閱讀 1306·2019-08-23 18:21
閱讀 2520·2019-08-23 18:18