【源码解析】hashMap源码跟进
hashMap的實(shí)現(xiàn)原理
-
Java8以前底層數(shù)據(jù)結(jié)構(gòu):數(shù)組+鏈表。
-
Java8及以后底層數(shù)據(jù)結(jié)構(gòu):數(shù)組+鏈表+紅黑樹。默認(rèn)情況下鏈表長(zhǎng)度超過8變成紅黑樹(整個(gè)hashMap元素?cái)?shù)量超過64),紅黑樹節(jié)點(diǎn)樹小于6變回鏈表。
hashMap是如何解決hash沖突的問題的
-
如果發(fā)生了碰撞,新添加的元素將以鏈表的方式鏈接到后面。
-
如果鏈表長(zhǎng)度超過閥值,就把鏈表轉(zhuǎn)成紅黑樹。
-
如果鏈表長(zhǎng)度低于6,就把紅黑樹轉(zhuǎn)回鏈表。
hashMap的擴(kuò)容
數(shù)組每個(gè)下標(biāo)對(duì)應(yīng)的位置稱為hash槽,默認(rèn)情況下,當(dāng)擁有元素的hash槽數(shù)量超過當(dāng)前容量乘以0.75,就會(huì)觸發(fā)擴(kuò)容操作,擴(kuò)容為當(dāng)前容量的2倍。
源碼翻看
hashMap的屬性
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {//序列號(hào),序列化的時(shí)候使用。private static final long serialVersionUID = 362498820763181265L;/*** 默認(rèn)容量,1向左移位4個(gè),00000001變成00010000,也就是2的4次方為16* 使用移位是因?yàn)橐莆皇怯?jì)算機(jī)基礎(chǔ)運(yùn)算,效率比加減乘除快。**/static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//最大容量,2的30次方。static final int MAXIMUM_CAPACITY = 1 << 30;//加載因子,用于擴(kuò)容使用。這個(gè)參數(shù)的意義是:當(dāng)數(shù)組長(zhǎng)度達(dá)到當(dāng)前長(zhǎng)度 * 0.75時(shí) 需要擴(kuò)容了!static final float DEFAULT_LOAD_FACTOR = 0.75f;//當(dāng)某個(gè)桶節(jié)點(diǎn)數(shù)量大于8時(shí),會(huì)轉(zhuǎn)換為紅黑樹。static final int TREEIFY_THRESHOLD = 8;//當(dāng)某個(gè)桶節(jié)點(diǎn)數(shù)量小于6時(shí),會(huì)轉(zhuǎn)換為鏈表,前提是它當(dāng)前是紅黑樹結(jié)構(gòu)。static final int UNTREEIFY_THRESHOLD = 6;//當(dāng)整個(gè)hashMap中元素?cái)?shù)量大于64時(shí),也會(huì)進(jìn)行轉(zhuǎn)為紅黑樹結(jié)構(gòu)。static final int MIN_TREEIFY_CAPACITY = 64;//存儲(chǔ)元素的數(shù)組,transient關(guān)鍵字表示該屬性不能被序列化transient Node<K,V>[] table;//將數(shù)據(jù)轉(zhuǎn)換成set的另一種存儲(chǔ)形式,這個(gè)變量主要用于迭代功能。transient Set<Map.Entry<K,V>> entrySet;//元素?cái)?shù)量transient int size;//統(tǒng)計(jì)該map修改的次數(shù)transient int modCount;//臨界值,也就是元素?cái)?shù)量達(dá)到臨界值時(shí),會(huì)進(jìn)行擴(kuò)容。int threshold;//也是加載因子,只不過這個(gè)是變量。final float loadFactor;構(gòu)造方法
構(gòu)造方法中 ,都是依靠第三個(gè)方法來執(zhí)行的,但是前三個(gè)方法都沒有進(jìn)行數(shù)組的初始化操作,即使調(diào)用了構(gòu)造方法此時(shí)存放HaspMap中數(shù)組元素的table表長(zhǎng)度依舊為0 。在第四個(gè)構(gòu)造方法中調(diào)用了inflateTable()方法完成了table的初始化操作,并將m中的元素添加到HashMap中。
/** * 構(gòu)造方法 1 無參構(gòu)造方法,使用默認(rèn)初始容量16與默認(rèn)負(fù)載因子0.75構(gòu)造一個(gè)空的HashMap。*/public HashMap() {// 初始化加載因子this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}/** * 構(gòu)造方法 2 傳入初始容量,通過默認(rèn)負(fù)載因子構(gòu)造一個(gè)空的HashMap* 調(diào)用了HashMap(int initialCapacity, float loadFactor)構(gòu)造方法。*/public HashMap(int initialCapacity) {// 調(diào)用構(gòu)造方法3,并傳入加載因子this(initialCapacity, DEFAULT_LOAD_FACTOR);}/** * 構(gòu)造方法 3 傳入初始容量和負(fù)載因子來構(gòu)造一個(gè)空的HashMap。*/public HashMap(int initialCapacity, float loadFactor) {// 初始容量不能小于0if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);// 初始容量不能大于MAXIMUM_CAPACITY(最大容量)if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;// 校驗(yàn)負(fù)載因子合法性if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " + loadFactor);this.loadFactor = loadFactor;// 計(jì)算下次resize的閾值this.threshold = tableSizeFor(initialCapacity);}/** * 構(gòu)造方法 4 指定集合,轉(zhuǎn)化為HashMap,使用默認(rèn)初始容量與默認(rèn)負(fù)載因子。*/public HashMap(Map<? extends K, ? extends V> m) {// 初始化加載因子this.loadFactor = DEFAULT_LOAD_FACTOR;// 將m中的所有元素添加至hashMap中putMapEntries(m, false);}final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {//獲取該map的實(shí)際長(zhǎng)度int s = m.size();if (s > 0) {//判斷table是否初始化,如果沒有初始化if (table == null) { // pre-size/*** 求出需要的容量,因?yàn)閷?shí)際使用的長(zhǎng)度=容量*0.75得來的,* +1是因?yàn)樾?shù)相除,基本都不會(huì)是整數(shù),容量大小不能為小數(shù)的,* 后面轉(zhuǎn)換為int,多余的小數(shù)就要被丟掉,所以+1,* 例如,map實(shí)際長(zhǎng)度22,22/0.75=29.3,所需要的容量肯定為30,* 如果剛剛好除得整數(shù)呢,除得整數(shù)的話,容量大小多1也沒什么影響**/float ft = ((float)s / loadFactor) + 1.0F;//判斷該容量大小是否超出上限。int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY);/*** 對(duì)臨界值進(jìn)行初始化,tableSizeFor(t)這個(gè)方法會(huì)返回大于t值的,且離其最近的2次冪,* 例如t為29,則返回的值是32**/if (t > threshold)threshold = tableSizeFor(t);}//如果table已經(jīng)初始化,則進(jìn)行擴(kuò)容操作,resize()就是擴(kuò)容。else if (s > threshold)resize();//遍歷,把map中的數(shù)據(jù)轉(zhuǎn)到hashMap中。for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {K key = e.getKey();V value = e.getValue();putVal(hash(key), key, value, false, evict);}}}擴(kuò)容方法
final Node<K,V>[] resize() {// 把之前的數(shù)組變成 oldTabNode<K,V>[] oldTab = table;//old 的長(zhǎng)度int oldCap = (oldTab == null) ? 0 : oldTab.length;//old 的臨界值int oldThr = threshold;//初始化new的長(zhǎng)度和臨界值int newCap, newThr = 0;//oldCap > 0也就是說不是首次初始化,因?yàn)閔ashMap用的是懶加載if (oldCap > 0) {// 大于最大值if (oldCap >= MAXIMUM_CAPACITY) {//臨界值為整數(shù)的最大值threshold = Integer.MAX_VALUE;return oldTab; // 不需要擴(kuò)容,直接返回 old}// 沒有超過最大值,擴(kuò)容兩倍,并且擴(kuò)容后的長(zhǎng)度要小于最大值,old 長(zhǎng)度也要大于16else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)// 臨界值擴(kuò)容為 old 的臨界值2倍newThr = oldThr << 1; }/*** 如果oldCap<0,但是已經(jīng)初始化了,像把元素刪除完之后的情況,那么它的臨界值肯定還存在,* 如果是首次初始化,它的臨界值則為0**/else if (oldThr > 0) // old 的臨界值 大于0newCap = oldThr;// 首次初始化,給與默認(rèn)的值else { newCap = DEFAULT_INITIAL_CAPACITY;// 臨界值 等于 容量 * 加載因子newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 初始化時(shí)容量小于默認(rèn)值16的,此時(shí)newThr沒有賦值,計(jì)算新的resize上限if (newThr == 0) {// new的臨界值float ft = (float)newCap * loadFactor;// 判斷是否new容量是否大于最大值,臨界值是否大于最大值newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}// 把上面各種情況分析出的臨界值,在此處真正進(jìn)行改變,也就是容量和臨界值都改變了。threshold = newThr;// 表示忽略該警告@SuppressWarnings({"rawtypes","unchecked"})// 初始化Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];// 賦予當(dāng)前的tabletable = newTab;// 此處是把old中的元素,遍歷到new中if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {// 臨時(shí)變量Node<K,V> e;// 當(dāng)前哈希桶的位置值不為null,也就是數(shù)組下標(biāo)處有值,因?yàn)橛兄当硎究赡軙?huì)發(fā)生沖突if ((e = oldTab[j]) != null) {// 把已經(jīng)賦值之后的變量置位null,為了好回收,釋放內(nèi)存oldTab[j] = null;// 如果下標(biāo)處的節(jié)點(diǎn)沒有下一個(gè)元素if (e.next == null)// 把該變量的值存入newCap中,e.hash & (newCap - 1)并不等于jnewTab[e.hash & (newCap - 1)] = e;// 該節(jié)點(diǎn)為紅黑樹結(jié)構(gòu),也就是存在哈希沖突,該哈希桶中有多個(gè)元素else if (e instanceof TreeNode)//把此樹進(jìn)行轉(zhuǎn)移到newCap中((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { /*** 此處表示為鏈表結(jié)構(gòu),同樣把鏈表轉(zhuǎn)移到newCap中,* 就是把鏈表遍歷后,把值轉(zhuǎn)過去,在置位null**/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;elseloTail.next = e;loTail = e;}// 原索引+oldCapelse {if (hiTail == null)hiHead = e;elsehiTail.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;}}}}}//返回?cái)U(kuò)容后的hashMapreturn newTab;}添加方法
public V put(K key, V value) {/*** 四個(gè)參數(shù),* 第一個(gè)hash值,* 第四個(gè)參數(shù)表示如果該key存在值,如果為null的話,則插入新的value,* 最后一個(gè)參數(shù),在hashMap中沒有用,可以不用管,使用默認(rèn)的即可**/return putVal(hash(key), key, value, false, true);}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {// tab 哈希數(shù)組,p 該哈希桶的首節(jié)點(diǎn),n hashMap的長(zhǎng)度,i 計(jì)算出的數(shù)組下標(biāo)Node<K,V>[] tab; Node<K,V> p; int n, i;// 獲取長(zhǎng)度并進(jìn)行擴(kuò)容,使用的是懶加載,table一開始是沒有加載的,等put后才開始加載if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;/*** 如果計(jì)算出的該哈希桶的位置沒有值,則把新插入的key-value放到此處,* 此處就算沒有插入成功,也就是發(fā)生哈希沖突時(shí)也會(huì)把哈希桶的首節(jié)點(diǎn)賦予p**/if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);//發(fā)生哈希沖突的幾種情況else {// e 臨時(shí)節(jié)點(diǎn)的作用, k 存放該當(dāng)前節(jié)點(diǎn)的key Node<K,V> e; K k;// 第一種,插入的key-value的hash值,key都與當(dāng)前節(jié)點(diǎn)的相等,e = p,則表示為首節(jié)點(diǎn)if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 第二種,hash值不等于首節(jié)點(diǎn),判斷該p是否屬于紅黑樹的節(jié)點(diǎn)else if (p instanceof TreeNode)/*** 為紅黑樹的節(jié)點(diǎn),則在紅黑樹中進(jìn)行添加,* 如果該節(jié)點(diǎn)已經(jīng)存在,則返回該節(jié)點(diǎn)(不為null),* 該值很重要,用來判斷put操作是否成功,如果添加成功返回null**/e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 第三種,hash值不等于首節(jié)點(diǎn),不為紅黑樹的節(jié)點(diǎn),則為鏈表的節(jié)點(diǎn)else {// 遍歷該鏈表for (int binCount = 0; ; ++binCount) {// 如果找到尾部,則表明添加的key-value沒有重復(fù),在尾部進(jìn)行添加if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 判斷是否要轉(zhuǎn)換為紅黑樹結(jié)構(gòu)if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash);break;}// 如果鏈表中有重復(fù)的key,e則為當(dāng)前重復(fù)的節(jié)點(diǎn),結(jié)束循環(huán)if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 在循環(huán)中判斷e是否為null,如果為null則表示加了一個(gè)新節(jié)點(diǎn),// 不是null則表示找到了hash、key都一致的Node。if (e != null) { V oldValue = e.value;// 判斷是否更新value值// map提供putIfAbsent方法,如果key存在,不更新value// 但是如果value==null任何情況下都更改此值if (!onlyIfAbsent || oldValue == null)e.value = value;// 此方法是空方法,什么都沒實(shí)現(xiàn),用戶可以根據(jù)需要進(jìn)行覆蓋afterNodeAccess(e);return oldValue;}}// 到了此步驟,則表明待插入的key-value是沒有key的重復(fù),因?yàn)椴迦氤晒節(jié)點(diǎn)的值為null// 修改次數(shù)+1++modCount;// 實(shí)際長(zhǎng)度+1,判斷是否大于臨界值,大于則擴(kuò)容if (++size > threshold)resize();// 此方法是空方法,什么都沒實(shí)現(xiàn),用戶可以根據(jù)需要進(jìn)行覆蓋afterNodeInsertion(evict);// 添加成功return null;}刪除方法
public V remove(Object key) {//臨時(shí)變量Node<K,V> e;/*** 調(diào)用removeNode(hash(key), key, null, false, true)進(jìn)行刪除,* 第三個(gè)value為null,表示,把key的節(jié)點(diǎn)直接都刪除了,不需要用到值,* 如果設(shè)為值,則還需要去進(jìn)行查找操作**/return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;}/*** 第一參數(shù)為哈希值,* 第二個(gè)為key,* 第三個(gè)value,* 第四個(gè)為是為true的話,則表示刪除它key對(duì)應(yīng)的value,不刪除key,* 第四個(gè)如果為false,則表示刪除后,不移動(dòng)節(jié)點(diǎn)**/final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {// tab 哈希數(shù)組,p 數(shù)組下標(biāo)的節(jié)點(diǎn),n 長(zhǎng)度,index 當(dāng)前數(shù)組下標(biāo)Node<K,V>[] tab; Node<K,V> p; int n, index;// 哈希數(shù)組不為null,且長(zhǎng)度大于0,然后獲得到要?jiǎng)h除key的節(jié)點(diǎn)所在是數(shù)組下標(biāo)位置if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {// nodee 存儲(chǔ)要?jiǎng)h除的節(jié)點(diǎn),e 臨時(shí)變量,k 當(dāng)前節(jié)點(diǎn)的key,v 當(dāng)前節(jié)點(diǎn)的valueNode<K,V> node = null, e; K k; V v;// 如果數(shù)組下標(biāo)的節(jié)點(diǎn)正好是要?jiǎng)h除的節(jié)點(diǎn),把值賦給臨時(shí)變量nodeif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;// 也就是要?jiǎng)h除的節(jié)點(diǎn),在鏈表或者紅黑樹上,先判斷是否為紅黑樹的節(jié)點(diǎn)else if ((e = p.next) != null) {if (p instanceof TreeNode)// 遍歷紅黑樹,找到該節(jié)點(diǎn)并返回node = ((TreeNode<K,V>)p).getTreeNode(hash, key);else { // 表示為鏈表節(jié)點(diǎn),一樣的遍歷找到該節(jié)點(diǎn)do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}/*** 注意,如果進(jìn)入了鏈表中的遍歷,那么此處的p不再是數(shù)組下標(biāo)的節(jié)點(diǎn),* 而是要?jiǎng)h除結(jié)點(diǎn)的上一個(gè)結(jié)點(diǎn)**/p = e;} while ((e = e.next) != null);}}// 找到要?jiǎng)h除的節(jié)點(diǎn)后,判斷!matchValue,我們正常的remove刪除,!matchValue都為trueif (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {// 如果刪除的節(jié)點(diǎn)是紅黑樹結(jié)構(gòu),則去紅黑樹中刪除if (node instanceof TreeNode)((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);// 如果是鏈表結(jié)構(gòu),且刪除的節(jié)點(diǎn)為數(shù)組下標(biāo)節(jié)點(diǎn),也就是頭結(jié)點(diǎn),直接讓下一個(gè)作為頭else if (node == p)tab[index] = node.next;else /*** 為鏈表結(jié)構(gòu),刪除的節(jié)點(diǎn)在鏈表中,把要?jiǎng)h除的下一個(gè)結(jié)點(diǎn)設(shè)為上一個(gè)結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)**/p.next = node.next;// 修改計(jì)數(shù)器++modCount;// 長(zhǎng)度減一--size;/*** 此方法在hashMap中是為了讓子類去實(shí)現(xiàn),主要是對(duì)刪除結(jié)點(diǎn)后的鏈表關(guān)系進(jìn)行處理**/afterNodeRemoval(node);// 返回刪除的節(jié)點(diǎn)return node;}}// 返回null則表示沒有該節(jié)點(diǎn),刪除失敗return null;}獲取方法
public V get(Object key) {Node<K,V> e;//也是調(diào)用getNode方法來完成的return (e = getNode(hash(key), key)) == null ? null : e.value;}final Node<K,V> getNode(int hash, Object key) {// first 頭結(jié)點(diǎn),e 臨時(shí)變量,n 長(zhǎng)度,k keyNode<K,V>[] tab; Node<K,V> first, e; int n; K k;// table不為空 && table長(zhǎng)度大于0 && table索引位置(根據(jù)hash值計(jì)算出)節(jié)點(diǎn)不為空if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// first的key等于傳入的key則返回first對(duì)象if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;//first的key不等于傳入的key則說明是鏈表,向下遍歷if ((e = first.next) != null) {// 判斷是否為TreeNode,是則為紅黑樹// 如果是紅黑樹節(jié)點(diǎn),則調(diào)用紅黑樹的查找目標(biāo)節(jié)點(diǎn)方法getTreeNodeif (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);do {//走下列步驟表示是鏈表,循環(huán)至節(jié)點(diǎn)的key與傳入的key值相等if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}//找不到符合的返回空return null;}計(jì)算哈希
static final int hash(Object key) {int h;// 如果key == null 則將數(shù)據(jù)存入下標(biāo)0的位置return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}總結(jié):
- 所以key值可以為null,存入下標(biāo)0的位置
- 默認(rèn)創(chuàng)建的hashmap默認(rèn)長(zhǎng)度為16
- HashMap使用的是懶加載,構(gòu)造完HashMap對(duì)象后,只要不進(jìn)行put 方法插入元素,HashMap并不會(huì)去初始化或者擴(kuò)容table。當(dāng)首次調(diào)用put方法時(shí),HashMap會(huì)發(fā)現(xiàn)table為空然后調(diào)用resize方法進(jìn)行初始化
總結(jié)
以上是生活随笔為你收集整理的【源码解析】hashMap源码跟进的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【java基础】map的基本使用与字符串
- 下一篇: 【理论】红黑树的实现原理