Java中的HashMap集合源碼詳細(xì)解讀
什么是Hash?
hash表是一種數(shù)據(jù)結(jié)構(gòu),它擁有驚人的效率,它的時(shí)間復(fù)雜度低到接近O(1)這樣的常數(shù)級(jí)。
hash表的實(shí)現(xiàn)主要是:
1.計(jì)算存儲(chǔ)位置的hash函數(shù)。
2.處理哈希沖突的方法。
3.hash的物理存儲(chǔ)。
hash函數(shù)
它的目的是通過(guò)一個(gè)key選出(映射)一個(gè)唯一的存儲(chǔ)地址。
最常見(jiàn)的hash函數(shù):f(key)=a*key+b 這里a,b為常數(shù)(不為0),f(key)就是計(jì)算出的哈希值 一般一個(gè)hash函數(shù)的設(shè)計(jì)好壞,直接影響到效率。
哈希沖突
hash沖突定義:當(dāng)兩個(gè)key相同時(shí)計(jì)算的hash值會(huì)一樣,導(dǎo)致沖突。
hash沖突解決部分方法: **開(kāi)放定地址:**找下一塊地址(另一個(gè)hash表(另一個(gè)未用過(guò)的hash值·),無(wú)限多) **再散列函數(shù)法:**一個(gè)hash函數(shù)解決不了,就兩個(gè),兩個(gè)不行就三個(gè)… **鏈地址法(hash桶(此處鏈表)的概念):**數(shù)組+鏈表,將具有相同hash的同義詞按次序放入鏈表,并鏈表存在數(shù)組。鏈表的hash值以數(shù)組hash值開(kāi)始確定新的hash(不擔(dān)心與其它數(shù)組的hash相同) 還有的就不做介紹了
物理存儲(chǔ)
物理存儲(chǔ)結(jié)構(gòu):順序|鏈?zhǔn)酱鎯?chǔ) hash表的主干永遠(yuǎn)都是一個(gè)數(shù)組 一般的hash只需要一個(gè)數(shù)組來(lái)存儲(chǔ),一般以數(shù)組下標(biāo)做hash值。 在鏈地址法中需要用到鏈表。
什么是Map?
Map在計(jì)算機(jī)概念是一個(gè)key-value(唯一性)鍵值對(duì)
什么是HashMap?
根據(jù)前面的鋪墊, hashMap的存儲(chǔ)主干是一個(gè)數(shù)組(源碼中的Node(有些是Entry)對(duì)象數(shù)組), Node(Entry)對(duì)象包含了Key-Value屬性
hashMap處理hash沖突:java1.8后,使用的是鏈地址法。
數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)如下:
在擁有鏈表的情況下,hashMap的查詢效率必然是低一些的,復(fù)雜度提高到o(n) 但1.8利用了紅黑樹(shù)數(shù)據(jù)結(jié)構(gòu),又將復(fù)雜度降為了o(Log(n))
HashMap的源碼分析
構(gòu)造函數(shù)(4個(gè))
部分成員變量:
- transient 關(guān)鍵字:再被修飾后變量序列化將不可見(jiàn)
- threshold:初始空間(initialCapacity傳入?yún)?shù))
- loadFactor:負(fù)載因子 modCount:是快速失敗的判斷標(biāo)準(zhǔn)(在迭代時(shí),其他線程訪問(wèn)Map,并導(dǎo)致其結(jié)構(gòu)改變,會(huì)拋異常)
- table:節(jié)點(diǎn)Set集合(HashMap主干,是個(gè)數(shù)組)
- initialCapacity默認(rèn)為16,loadFactory默認(rèn)為0.75
負(fù)載因子在0.75時(shí)效率最好(數(shù)學(xué)統(tǒng)計(jì)學(xué)驗(yàn)證),用于衡量空間利用的方法選擇
hashMap會(huì)擴(kuò)容且容量永遠(yuǎn)是2的冪
合理判斷參數(shù),就結(jié)束構(gòu)造 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } 合理判斷空間大小參數(shù),使用默認(rèn)負(fù)載參數(shù)就結(jié)束構(gòu)造 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /使用默認(rèn)參數(shù)就結(jié)束構(gòu)造 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } 構(gòu)造一個(gè)具有相同的映射關(guān)系與指定Map一個(gè)新的HashMap。 HashMap中與默認(rèn)負(fù)載因數(shù)(0.75)和初始容量足以容納在指定的地圖的映射創(chuàng)建 public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
發(fā)現(xiàn)此時(shí)table變量未分配空間。根據(jù)put函數(shù)源碼發(fā)現(xiàn),table變量在put()分配空間
再看關(guān)鍵對(duì)象Node(Entry)
//Map.Entry<K,V>是一個(gè)接口 //Node static class Node<K,V> implements Map.Entry<K,V> { final int hash;//哈希值 final K key;//鍵值 V value; Node<K,V> next;//下一節(jié)點(diǎn);因?yàn)槭擎湹刂贩? Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } //other }
再看其部分關(guān)鍵函數(shù) hash() : key.hashCode()產(chǎn)生依靠equals()方法,位移異或等操作的哈希值 ,這個(gè)根據(jù)版本不同也計(jì)算方法不同,但是hashCode()幾乎一直存在 put() : 計(jì)算hash值然后對(duì)table
/*單位計(jì)算key.hashCode()和差(異或)的散列以降低的較高位。 因?yàn)楸硎褂霉β实膬裳诒危咨⒘械?,只有在?dāng)前的掩模將總是碰撞上述位變化。 (其中著名的例子是一組浮動(dòng)鍵的小桌子控股連續(xù)整數(shù))。 所以我們施加向下變換利差較高位的影響。 有速度,實(shí)用,和位傳播質(zhì)量之間的權(quán)衡。 由于哈希許多共同的集已合理分配(所以不要蔓延受益),因?yàn)槲覀冇脴?shù)來(lái)處理大型成套碰撞的垃圾箱, 我們只是XOR一些最便宜的方式轉(zhuǎn)移位降低系統(tǒng)lossage, 以及納入,否則將永遠(yuǎn)不會(huì)因?yàn)楸斫绲闹笖?shù)計(jì)算中使用的最高位的影響。 */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } /** * Implements Map.put and related methods * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)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; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } //快速失敗 ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
JDK1.8在JDK1.7的基礎(chǔ)上針對(duì)增加了紅黑樹(shù)來(lái)進(jìn)行優(yōu)化。即當(dāng)鏈表超過(guò)8時(shí),鏈表就轉(zhuǎn)換為紅黑樹(shù),利用紅黑樹(shù)快速增刪改查的特點(diǎn)提高HashMap的性能,其中會(huì)用到紅黑樹(shù)的插入、刪除、查找等算法。
看上面的put函數(shù)的putVal()就含有紅黑樹(shù)的插入方法, 一開(kāi)始普通的鏈表不需要紅黑樹(shù)。 下列源碼可以看出, 轉(zhuǎn)換紅黑樹(shù)條件是 鏈表(是在每次遍歷到數(shù)組時(shí)每個(gè)單元對(duì)于的鏈表,參考上圖)節(jié)點(diǎn)數(shù)大于等于8且數(shù)組長(zhǎng)度大于等于64,
詳見(jiàn)方法:treeifyBin();
//判斷處理方法 final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
//紅黑樹(shù)構(gòu)造類(lèi) static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red;//是紅節(jié)點(diǎn)嗎 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } }
HashMap源碼探究到此為止,以后可以繼續(xù)深入,學(xué)習(xí)紅黑樹(shù)的實(shí)際應(yīng)用等等
在此附上一張網(wǎng)上Copy過(guò)來(lái)的圖: 是講1.8版本的HashMap在增加元素后一系列的操作步驟,以及優(yōu)化方式流程圖
集合源碼魅力無(wú)窮。 踏踏實(shí)實(shí)讀源碼。
到此這篇關(guān)于Java中的HashMap集合源碼詳細(xì)解讀的文章就介紹到這了,更多相關(guān)HashMap集合源碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java MAVEN 工程pom配置報(bào)錯(cuò)解決方案
這篇文章主要介紹了Java MAVEN 工程pom配置報(bào)錯(cuò)解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10關(guān)于java中基本數(shù)據(jù)類(lèi)型的數(shù)值范圍
這篇文章主要介紹了關(guān)于java中基本數(shù)據(jù)類(lèi)型的數(shù)值范圍,基本類(lèi)型,或者叫做內(nèi)置類(lèi)型,是JAVA中不同于類(lèi)的特殊類(lèi)型,它們是我們編程中使用最頻繁的類(lèi)型,需要的朋友可以參考下2023-07-07SpringBoot ResponseEntity標(biāo)識(shí)Http響應(yīng)方式
這篇文章主要介紹了SpringBoot ResponseEntity標(biāo)識(shí)Http響應(yīng)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07Java獲取時(shí)間打印到控制臺(tái)代碼實(shí)例
這篇文章主要介紹了Java獲取時(shí)間打印到控制臺(tái)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02SpringBoot集成POI實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出的示例詳解
Apache?POI?是用Java編寫(xiě)的免費(fèi)開(kāi)源的跨平臺(tái)的?Java?API,Apache?POI提供API給Java程序?qū)icrosoft?Office格式檔案讀和寫(xiě)的功能。本文主要介紹通過(guò)SpringBoot集成POI工具實(shí)現(xiàn)Excel的導(dǎo)入和導(dǎo)出功能,需要的可以參考一下2022-07-07SpringBoot項(xiàng)目中使用Mockito的示例代碼
這篇文章主要介紹了SpringBoot項(xiàng)目中使用Mockito的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10