原始碼-JDK1.8HashMap擴容方法resize()解析
1、準備知識
-
HashMap的底層資料結構
Java語言的基本資料結構可以分為兩種,一種是陣列,另一種的模擬指標/引用,Java語言中涉及到的資料結構都是這兩種的擴充。JDK1.8之前HashMap是陣列+連結串列結合的連結串列雜湊。JDK1.8在解決雜湊衝突上發生了變化,當連結串列長度大於閾值/預設8的時候,會將連結串列轉化為紅黑樹,減少搜尋時間。
-
hash演算法
我們希望HashMap的元素位置儘量分散,最好是每個位置只有一個元素,這樣用hash演算法求得該位置後可以直接返回結果,不用再遍歷連結串列/紅黑樹。
HashMap通過Key的hashCode經過擾動函式(就是hash方法)處理後得到hash值,然後在公式(n-1)& hash (/n是陣列長度)判斷當前元素的位置是否已存在元素,若已存在則判斷新加入的元素和已存在的是否相同,相同則覆蓋,不相同則拉鍊法解決衝突。
//擾動函式 == hash方法 /*JDK1.7 此函式可確保僅在以下方面不同的hashCode每個位位置的恆定倍數有界碰撞次數(預設負荷係數下約為8)。 */ static int hash(int h){ h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /*JDK1.8的hash方法比JDK1.7的更加簡化,JDK1.7的擾動需要4次,JDK1.8的僅需1次*/ static int hash(Object key){ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
-
拉鍊法:將連結串列和陣列相結合。也就是說建立⼀個連結串列陣列,陣列中每⼀格就是⼀個連結串列。若遇到雜湊衝突,則將衝突的值加到連結串列中即可。下次查詢的時候,需要遍歷這個連結串列找到元素,減慢了查詢效率。
-
->所有,在儲存大規模的資料時,預先指定HashMap的大小為2的冪次方
int capacity = 1; while(capacity < initialCapacity){ capacity <<= 1;//擴大兩倍 }
2、HashMap的resize()方法介紹
-
當HashMap中的元素增多時,發生雜湊衝突的機率就越來越高,這時候為了提高查詢的效率,需要對陣列大小進行擴容。
-
當陣列的元素規模 > 陣列大小 * loadFactory時進行擴容,loadFactory預設是0.75
... map = new HashMap<>(c, loadFactor); //HashMap提供了以上有參構造方法 //initialCapacity是HashMap陣列的初始容量 //loadFactor是載入因子
-
什麼時候進行resize?1、初始化table的時候 2、陣列元素size超出threshold = map.size() * loadFactory
直接擴容 原陣列的2倍大小
-
節點在轉移的過程中是一個個節點複製還是一串一串的轉移?從原始碼中我們可以看出,擴容時是先找到拆分後處於同一個桶的節點,將這些節點連線好,然後把頭節點存入桶中即可
-
話不多說,上原始碼
-
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; //如果原table不為空 if (oldCap > 0) { //如果原容量已經達到最大容量了,無法進行擴容,直接返回 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //設定新容量為舊容量的兩倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //閾值也變為原來的兩倍 newThr = oldThr << 1; // double threshold } /** * 從構造方法我們可以知道 * 如果沒有指定initialCapacity, 則不會給threshold賦值, 該值被初始化為0 * 如果指定了initialCapacity, 該值被初始化成大於initialCapacity的最小的2的次冪 * 這裡這種情況指的是原table為空,並且在初始化的時候指定了容量, * 則用threshold作為table的實際大小 */ else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //構造方法中沒有指定容量,則使用預設值 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 計算指定了initialCapacity情況下的新的 threshold if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; /**從以上操作我們知道, 初始化HashMap時, * 如果建構函式沒有指定initialCapacity, 則table大小為16 * 如果建構函式指定了initialCapacity, 則table大小為threshold, * 即大於指定initialCapacity的最小的2的整數次冪 * 從下面開始, 初始化table或者擴容, 實際上都是通過新建一個table來完成 */ @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { /** 這裡注意, table中存放的只是Node的引用,這裡將oldTab[j]=null只是清除舊錶的引用, * 但是真正的node節點還在, 只是現在由e指向它 */ oldTab[j] = null; //桶中只有一個節點,直接放入新桶中 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //桶中為紅黑樹,則對樹進行拆分,對樹的操作有機會再講 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //桶中為連結串列,對連結串列進行拆分 else { // preserve order //下面為對連結串列的拆分,我們單獨來講一下。 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> 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; }
-
resize()操作之連結串列拆分
/** 這裡定義了四個節點:loHead, loTail ,hiHead , hiTail,兩個頭節點兩個尾節點 選擇出擴容後在同一個桶中的節點,直接將節點連線好頭節點入桶即可。 **/ HashMap.Node<K,V> loHead = null, loTail = null; HashMap.Node<K,V> hiHead = null, hiTail = null; HashMap.Node<K,V> 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; }
-
-
相關文章
- ConcurrentHashMap原始碼解析,多執行緒擴容HashMap原始碼執行緒
- 死磕 java集合之ConcurrentHashMap原始碼分析(二)——擴容全解析JavaHashMap原始碼
- 從原始碼解析 Go 的切片型別以及擴容機制原始碼Go型別
- HashMap擴容機制原始碼分析HashMap原始碼
- 一對一直播平臺原始碼,該擴容時就擴容原始碼
- Spring原始碼之容器的功能擴充套件和refresh方法解析Spring原始碼套件
- Glide原始碼解析二---into方法IDE原始碼
- external-resizer 原始碼分析/pvc 擴容分析原始碼
- HashMap自動擴容機制原始碼詳解HashMap原始碼
- 聊聊Dubbo – Dubbo可擴充套件機制原始碼解析套件原始碼
- 擴充套件包原始碼解析 - PHP-Vars-To-JS-Transformer套件原始碼PHPJSORM
- dubbo原始碼解析-叢集容錯架構設計原始碼架構
- JDK1.8_HashMap原始碼__tableSizeFor方法解析JDKHashMap原始碼
- ArrayList 原始碼分析 -- 擴容問題及序列化問題原始碼
- ArrayList 原始碼分析 — 擴容問題及序列化問題原始碼
- 超詳細的ArrayList擴容過程(配合原始碼詳解)原始碼
- 【Linux】分割槽向左擴容的方法Linux
- 【資料結構】31、hashmap=》resize 擴容,不測不知道,一測嚇一跳資料結構HashMap
- Java Timer原始碼解析(定時器原始碼解析)Java原始碼定時器
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- ConcurrentHashMap1.8原始碼學習之擴容(連結串列結構)HashMap原始碼
- Java ArrayList原始碼分析(含擴容機制等重點問題分析)Java原始碼
- [原始碼解析] 並行分散式框架 Celery 之 容錯機制原始碼並行分散式框架
- ReactNative原始碼解析-初識原始碼React原始碼
- 通過原始碼一步一步分析 ArrayList 擴容機制原始碼
- 一對一聊天原始碼,你是否瞭解ERedis的擴容機制?原始碼Redis
- Koa 原始碼解析原始碼
- Koa原始碼解析原始碼
- RxPermission原始碼解析原始碼
- Express原始碼解析Express原始碼
- redux原始碼解析Redux原始碼
- CopyOnWriteArrayList原始碼解析原始碼
- LeakCanary原始碼解析原始碼
- ArrayBlockQueue原始碼解析BloC原始碼
- ReentrantLock原始碼解析ReentrantLock原始碼
- OKio原始碼解析原始碼
- ReentrantReadWriteLock原始碼解析原始碼
- CyclicBarrier原始碼解析原始碼