亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術(shù)文章
文章詳情頁

深入理解Java中的HashMap

瀏覽:75日期:2022-08-10 18:43:04
目錄一、HashMap的結(jié)構(gòu)圖示二、HashMap的成員變量以及含義2.1、hash方法說明2.2、tableSizeFor方法說明三、HashMap的構(gòu)造方法四、HashMap元素在數(shù)組中的位置五、HashMap的put方法分析5.1、put方法源碼分析5.2、put方法執(zhí)行過程總結(jié)六、HashMap的resize方法分析6.1、resize方法源碼6.2、(e.hash & oldCap) == 0分析6.3、部分代碼理解6.4、resize總結(jié)七、HashMap的get方法分析7.1、get方法源碼一、HashMap的結(jié)構(gòu)圖示

​本文主要說的是jdk1.8版本中的實(shí)現(xiàn)。而1.8中HashMap是數(shù)組+鏈表+紅黑樹實(shí)現(xiàn)的,大概如下圖所示。后面還是主要介紹Hash Map中主要的一些成員以及方法原理。

深入理解Java中的HashMap

​那么上述圖示中的結(jié)點(diǎn)Node具體類型是什么,源碼如下。Node是HashMap的內(nèi)部類,實(shí)現(xiàn)了Map.Entery接口,主要就是存放我們put方法所添加的元素。其中的next就表示這可以構(gòu)成一個單向鏈表,這主要是通過鏈地址法解決發(fā)生hash沖突問題。而當(dāng)桶中的元素個數(shù)超過閾值的時候就換轉(zhuǎn)為紅黑樹。

//hash桶中的結(jié)點(diǎn)Node,實(shí)現(xiàn)了Map.Entrystatic class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; //鏈表的next指針 Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next; } public final K getKey(){ return key; } public final V getValue() { return value; } public final String toString() { return key + '=' + value; } //重寫Object的hashCode public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue; } //equals方法 public final boolean equals(Object o) {if (o == this) return true;if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false; }}//轉(zhuǎn)變?yōu)榧t黑樹后的結(jié)點(diǎn)類static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> { TreeNode<k,v> parent; // 父節(jié)點(diǎn) TreeNode<k,v> left; //左子樹 TreeNode<k,v> right;//右子樹 TreeNode<k,v> prev; // needed to unlink next upon deletion boolean red; //顏色屬性 TreeNode(int hash, K key, V val, Node<k,v> next) {super(hash, key, val, next); } //返回當(dāng)前節(jié)點(diǎn)的根節(jié)點(diǎn) final TreeNode<k,v> root() {for (TreeNode<k,v> r = this, p;;) { if ((p = r.parent) == null)return r; r = p;} }}

​上面只是大概了解了一下HashMap的簡單組成,下面主要介紹其中的一些參數(shù)和重要的方法原理實(shí)現(xiàn)。

二、HashMap的成員變量以及含義

//默認(rèn)初始化容量初始化=16static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//最大容量 = 1 << 30static final int MAXIMUM_CAPACITY = 1 << 30;//默認(rèn)加載因子.一般HashMap的擴(kuò)容的臨界點(diǎn)是當(dāng)前HashMap的大小 > DEFAULT_LOAD_FACTOR * //DEFAULT_INITIAL_CAPACITY = 0.75F * 16static final float DEFAULT_LOAD_FACTOR = 0.75f;//當(dāng)hash桶中的某個bucket上的結(jié)點(diǎn)數(shù)大于該值的時候,會由鏈表轉(zhuǎn)換為紅黑樹static final int TREEIFY_THRESHOLD = 8;//當(dāng)hash桶中的某個bucket上的結(jié)點(diǎn)數(shù)小于該值的時候,紅黑樹轉(zhuǎn)變?yōu)殒湵韘tatic final int UNTREEIFY_THRESHOLD = 6;//桶中結(jié)構(gòu)轉(zhuǎn)化為紅黑樹對應(yīng)的table的最小大小static final int MIN_TREEIFY_CAPACITY = 64;//hash算法,計(jì)算傳入的key的hash值,下面會有例子說明這個計(jì)算的過程static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);} //tableSizeFor(initialCapacity)返回大于initialCapacity的最小的二次冪數(shù)值。下面會有例子說明static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}//hash桶transient Node<K,V>[] table;//保存緩存的entrySettransient Set<Map.Entry<K,V>> entrySet;//桶的實(shí)際元素個數(shù) != table.lengthtransient int size;//擴(kuò)容或者更改了map的計(jì)數(shù)器。含義:表示這個HashMap結(jié)構(gòu)被修改的次數(shù),結(jié)構(gòu)修改是那些改變HashMap中的映射數(shù)量或者//修改其內(nèi)部結(jié)構(gòu)(例如,重新散列rehash)的修改。 該字段用于在HashMap失敗快速(fast-fail)的Collection-views//上創(chuàng)建迭代器。transient int modCount;//臨界值,當(dāng)實(shí)際大小(cap*loadFactor)大于該值的時候,會進(jìn)行擴(kuò)充int threshold;//加載因子final float loadFactor;2.1、hash方法說明

//hash算法static final int hash(Object key) { int h; //key == null : 返回hash=0 //key != null //(1)得到key的hashCode:h=key.hashCode() //(2)將h無符號右移16位 //(3)異或運(yùn)算:h ^ h>>>16 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}

​假設(shè)現(xiàn)在我們向一個map中添加元素,例如map.put('fsmly','test'),那么其中key為'fsmly'的hashCode的二進(jìn)制表示為0000_0000_0011_0110_0100_0100_1001_0010,按照上面的步驟來計(jì)算,那么我們調(diào)用hash算法得到的hash值為:‭

深入理解Java中的HashMap

2.2、tableSizeFor方法說明

​該方法的作用就是:返回大于initialCapacity的最小的二次冪數(shù)值。如下實(shí)例

//n=cap-1=5; 5的二進(jìn)制0101B。>>> 操作符表示無符號右移,高位取0//n |= n>>>1: (1)n=0101 | 0101>>>1; (2)n=0101 | 0010; (3)n = 0111B //n |= n>>>2: (1)n=0111 | 0111>>>2; (2)n=0111 | 0011; (3)n = 0111B//n |= n>>>4: (1)n=0111 | 0111>>>4; (2)n=0111 | 0000; (3)n = 0111B//n |= n>>>8: (1)n=0111 | 0111>>>8; (2)n=0111 | 0000; (3)n = 0111B//n |= n>>>16:(1)n=0111 | 0111>>>16;(2)n=0111 | 0000; (3)n = 0111Bstatic final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; //n<0返回1 //n>最大容量,返回最大容量 //否則返回n+1(0111B+1B=1000B=8) return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

​再看下面這個:

//至于這里為什么減1,當(dāng)傳入的cap為2的整數(shù)次冪的時候,減1即保證最后的計(jì)算結(jié)果還是cap,而不是大于cap的另一個2的//整數(shù)次冪,例如我們傳入cap=16=10000B.按照上面那樣計(jì)算//n=cap-1=15=1111B.按照上面的方法計(jì)算得到:// n |= n>>>1: n=1111|0111=1111;后面還是相同的結(jié)果最后n=1111B=15.//所以返回的時候?yàn)閞eturn 15+1;int n = cap - 1; 三、HashMap的構(gòu)造方法

​我們看看HashMap源碼中為我們提供的四個構(gòu)造方法。我們可以看到,平常我們最常用的無參構(gòu)造器內(nèi)部只是僅僅初始化了loadFactor,別的都沒有做,底層的數(shù)據(jù)結(jié)構(gòu)則是延遲到插入鍵值對時再進(jìn)行初始化,或者說在resize中會做。后面說到擴(kuò)容方法的實(shí)現(xiàn)的時候會講到。

//(1)參數(shù)為初始化容量和加載因子的構(gòu)造函數(shù)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); //閾值為大于initialCapacity的最小二次冪}//(2)只給定初始化容量,那么加載因子就是默認(rèn)的加載因子:0.75public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR);}//(3)加載因子為默認(rèn)的加載因子,但是這個時候的初始化容量是沒有指定的,后面調(diào)用put或者get方法的時候才resizepublic HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}//(4)將傳遞的map中的值調(diào)用putMapEntries加入新的map集合中,其中加載因子是默認(rèn)的加載因子public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false);}四、HashMap元素在數(shù)組中的位置

​不管增加、刪除、查找鍵值對,定位到哈希桶數(shù)組的索引都是很關(guān)鍵的第一步,所以我們看看源碼怎樣通過hash()方法以及其他代碼確定一個元素在hash桶中的位置的。

//計(jì)算map中key的hash值static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}//這一小段代碼就是定位元素在桶中的位置。具體做法就是:容量n-1 & hash. //其中n是一個2的整數(shù)冪,而(n - 1) & hash其實(shí)質(zhì)就是hash%n,但//是取余運(yùn)算的效率不如位運(yùn)算與,并且(n - 1) & hash也能保證散列均勻,不會產(chǎn)生只有偶數(shù)位有值的現(xiàn)象p = tab[i = (n - 1) & hash];

​下面我們通過一個例子計(jì)算一下上面這個定位的過程,假設(shè)現(xiàn)在桶大小n為16.

深入理解Java中的HashMap

​我們可以看到,這里的hash方法并不是用原有對象的hashcode最為最終的hash值,而是做了一定位運(yùn)算,大概因?yàn)槿绻?n-1)的值太小的話,(n - 1) & hash的值就完全依靠hash的低位值,比如n-1為0000 1111,那么最終的值就完全依賴于hash值的低4位了,這樣的話hash的高位就玩完全失去了作用,h ^ (h >>> 16),通過這種方式,讓高位數(shù)據(jù)與低位數(shù)據(jù)進(jìn)行異或,也是變相的加大了hash的隨機(jī)性,這樣就不單純的依賴對象的hashcode方法了。

五、HashMap的put方法分析5.1、put方法源碼分析

public V put(K key, V value) { return putVal(hash(key), key, value, false, true);}final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //table == null 或者table的長度為0,調(diào)用resize方法進(jìn)行擴(kuò)容 //這里也說明:table 被延遲到插入新數(shù)據(jù)時再進(jìn)行初始化 if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length; // 這里就是調(diào)用了Hash算法的地方,具體的計(jì)算可參考后面寫到的例子 //這里定位坐標(biāo)的做法在上面也已經(jīng)說到過 if ((p = tab[i = (n - 1) & hash]) == null)// 如果計(jì)算得到的桶下標(biāo)值中的Node為null,就新建一個Node加入該位置(這個新的結(jié)點(diǎn)是在//table數(shù)組中)。而該位置的hash值就是調(diào)用hash()方法計(jì)算得到的key的hash值tab[i] = newNode(hash, key, value, null); //這里表示put的元素用自己key的hash值計(jì)算得到的下表和桶中的第一個位置元素產(chǎn)生了沖突,具體就是 //(1)key相同,value不同 //(2)只是通過hash值計(jì)算得到的下標(biāo)相同,但是key和value都不同。這里處理的方法就是鏈表和紅黑樹 else {Node<K,V> e; K k;//上面已經(jīng)計(jì)算得到了該hash對應(yīng)的下標(biāo)i,這里p=tab[i]。這里比較的有://(1)tab[i].hash是否等于傳入的hash。這里的tab[i]就是桶中的第一個元素//(2)比較傳入的key和該位置的key是否相同//(3)如果都相同,說明是同一個key,那么直接替換對應(yīng)的value值(在后面會進(jìn)行替換)if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //將桶中的第一個元素賦給e,用來記錄第一個位置的值 e = p;//這里判斷為紅黑樹。hash值不相等,key不相等;為紅黑樹結(jié)點(diǎn)else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //加入紅黑樹//判斷為鏈表結(jié)點(diǎn)else { for (int binCount = 0; ; ++binCount) {//如果達(dá)到鏈表的尾部if ((e = p.next) == null) { //在尾部插入新的結(jié)點(diǎn) p.next = newNode(hash, key, value, null); //前面的binCount是記錄鏈表長度的,如果該值大于8,就會轉(zhuǎn)變?yōu)榧t黑樹 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash); break;}//如果在遍歷鏈表的時候,判斷得出要插入的結(jié)點(diǎn)的key和鏈表中間的某個結(jié)點(diǎn)的key相//同,就跳出循環(huán),后面也會更新舊的value值if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break;//e = p.next。遍歷鏈表所用p = e; }}//判斷插入的是否存在HashMap中,上面e被賦值,不為空,則說明存在,更新舊的鍵值對if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null)e.value = value; //用傳入的參數(shù)value更新舊的value值 afterNodeAccess(e); return oldValue; //返回舊的value值} } //modCount修改 ++modCount; //容量超出就擴(kuò)容 if (++size > threshold)resize(); afterNodeInsertion(evict); return null;}5.2、put方法執(zhí)行過程總結(jié)

​可以看到主要邏輯在put方法中調(diào)用了putVal方法,傳遞的參數(shù)是調(diào)用了hash()方法計(jì)算key的hash值,主要邏輯在putVal中。可以結(jié)合注釋熟悉這個方法的執(zhí)行,我在這里大概總結(jié)一下這個方法的執(zhí)行:

1.首先 (tab = table) == null || (n = tab.length) == 0這一塊判斷hash桶是否為null,如果為null那么會調(diào)用resize方法擴(kuò)容。后面我們會說到這個方法

2.定位元素在桶中的位置,具體就是通過key的hash值和hash桶的長度計(jì)算得到下標(biāo)i,如果計(jì)算到的位置處沒有元素(null),那么就新建結(jié)點(diǎn)然后添加到該位置。

3.如果table[i]處不為null,已經(jīng)有元素了,那么就表明產(chǎn)生hash沖突,這里可能是三種情況

①判斷key是不是一樣,如果key一樣,那么就將新的值替換舊的值;

②如果不是因?yàn)閗ey一樣,那么需要判斷當(dāng)前該桶是不是已經(jīng)轉(zhuǎn)為了紅黑樹,是的話就構(gòu)造一個TreeNode結(jié)點(diǎn)插入紅黑樹;

③不是紅黑樹,就使用鏈地址法處理沖突問題。這里主要就是遍歷鏈表,如果在遍歷過程中也找到了key一樣的元素,那么久還是使用新值替換舊值。否則會遍歷到鏈表結(jié)尾處,到這里就直接新添加一個Node結(jié)點(diǎn)插入鏈表,插入之后還需要判斷是不是已將超過了轉(zhuǎn)換為紅黑樹的閾值8,如果超過就會轉(zhuǎn)為紅黑樹。

4.最后需要修改modCount的值。

5.判斷插入后的size大小是不是超過了threshhold,如果超過需要進(jìn)行擴(kuò)容。

上面很多地方都涉及到了擴(kuò)容,所以下面我們首先看看擴(kuò)容方法。

六、HashMap的resize方法分析6.1、resize方法源碼

​擴(kuò)容(resize)就是重新計(jì)算容量,具體就是當(dāng)map內(nèi)部的size大于DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY ,就需要擴(kuò)大數(shù)組的長度,以便能裝入更多的元素。resize方法實(shí)現(xiàn)中是使用一個新的數(shù)組代替已有的容量小的數(shù)組。

//該方法有2種使用情況:1.初始化哈希表(table==null) 2.當(dāng)前數(shù)組容量過小,需擴(kuò)容final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; //oldTab指向舊的table數(shù)組 //oldTab不為null的話,oldCap為原table的長度 //oldTab為null的話,oldCap為0 int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; //閾值 int newCap, newThr = 0; if (oldCap > 0) { //這里表明oldCap!=0,oldCap=原table.length();if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; //如果大于最大容量了,就賦值為整數(shù)最大的閥值 return oldTab;}// 如果數(shù)組的長度在擴(kuò)容后小于最大容量 并且oldCap大于默認(rèn)值16(這里的newCap也是在原來的//長度上擴(kuò)展兩倍)else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold雙倍擴(kuò)展threshhold } else if (oldThr > 0) // initial capacity was placed in threshold//這里的oldThr=tabSizeFor(initialCapacity),從上面的構(gòu)造方法看出,如果不是調(diào)用的//無參構(gòu)造,那么threshhold肯定都會是經(jīng)過tabSizeFor運(yùn)算得到的2的整數(shù)次冪的,所以可以將//其作為Node數(shù)組的長度(個人理解)newCap = oldThr; else { // zero initial threshold signifies using defaults(零初始閾值表示使用默認(rèn)值)//這里說的是我們調(diào)用無參構(gòu)造函數(shù)的時候(table == null,threshhold = 0),新的容量等于默//認(rèn)的容量,并且threshhold也等于默認(rèn)加載因子*默認(rèn)初始化容量newCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 計(jì)算新的resize上限 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'}) //以新的容量作為長度,創(chuàng)建一個新的Node數(shù)組存放結(jié)點(diǎn)元素 //當(dāng)然,桶數(shù)組的初始化也是在這里完成的 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; //原來的table不為null if (oldTab != null) {// 把每個bucket都移動到新的buckets中for (int j = 0; j < oldCap; ++j) { Node<K,V> e; //原table中下標(biāo)j位置不為null if ((e = oldTab[j]) != null) {oldTab[j] = null; //將原來的table[j]賦為null,及時GC?if (e.next == null) //如果該位置沒有鏈表,即只有數(shù)組中的那個元素 //通過新的容量計(jì)算在新的table數(shù)組中的下標(biāo):(n-1)&hash newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) //如果是紅黑樹結(jié)點(diǎn),重新映射時,需要對紅黑樹進(jìn)行拆分 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // 鏈表優(yōu)化重hash的代碼塊 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do {//上面判斷不是紅黑樹,那就是鏈表,這里就遍歷鏈表,進(jìn)行重新映射next = e.next;// 原位置if ((e.hash & oldCap) == 0) { //loTail處為null,那么直接加到該位置 if (loTail == null) loHead = e; //loTail為鏈表尾結(jié)點(diǎn),添加到尾部 elseloTail.next = e; //添加后,將loTail指向鏈表尾部,以便下次從尾部添加 loTail = e;}// 原位置+舊容量else { //hiTail處為null,就直接點(diǎn)添加到該位置 if (hiTail == null)hiHead = e; //hiTail為鏈表尾結(jié)點(diǎn),尾插法添加 elsehiTail.next = e; hiTail = e;} } while ((e = next) != null); // 將分組后的鏈表映射到新桶中 // 原索引放到bucket里 if (loTail != null) {//舊鏈表遷移新鏈表,鏈表元素相對位置沒有變化; //實(shí)際是對對象的內(nèi)存地址進(jìn)行操作 loTail.next = null;//鏈表尾元素設(shè)置為nullnewTab[j] = loHead; //數(shù)組中位置為j的地方存放鏈表的head結(jié)點(diǎn) } // 原索引+oldCap放到bucket里 if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead; }} }} } return newTab;}6.2、(e.hash & oldCap) == 0分析

​我這里添加上一點(diǎn),就是為什么使用 (e.hash & oldCap) == 0判斷是處于原位置還是放在更新的位置(原位置+舊容量),解釋如下:我們知道capacity是2的冪,所以oldCap為10...0的二進(jìn)制形式(比如16=10000B)。

(1)若判斷條件為真,意味著oldCap為1的那位對應(yīng)的hash位為0(1&0=0,其他位都是0,結(jié)果自然是0),對新索引的計(jì)算沒有影響,至于為啥沒影響下面就說到了。先舉個例子計(jì)算一下數(shù)組中的下標(biāo)在擴(kuò)容前后的變化:

深入理解Java中的HashMap

​從上面計(jì)算發(fā)現(xiàn),當(dāng)cap為1的那位對應(yīng)的hash為0的時候,resize前后的index是不變的。我們再看下面,使用上面的hash值,對應(yīng)的就是 (e.hash & oldCap) == 0,恰好也是下標(biāo)不變的

深入理解Java中的HashMap

​(2)若判斷條件為假,則 oldCap為1的那位對應(yīng)的hash位為1。比如新下標(biāo)=hash&( newCap-1 )= hash&( (16<<2) - 1)=10010,相當(dāng)于多了10000,即 oldCap .如同下面的例子

深入理解Java中的HashMap

​從上面計(jì)算發(fā)現(xiàn),當(dāng)cap為1的那位對應(yīng)的hash為1的時候,resize前后的index是改變的。我們再看下面,使用上面的hash值,對應(yīng)的就是 (e.hash & oldCap) != 0,恰好下標(biāo)就是原索引+原容量

深入理解Java中的HashMap

6.3、部分代碼理解

​這一部分其實(shí)和put方法中,使用鏈地址法解決hash沖突的原理差不多,都是對鏈表的操作。

// 原位置if ((e.hash & oldCap) == 0) { //loTail處為null,那么直接加到該位置 if (loTail == null) loHead = e; //loTail為鏈表尾結(jié)點(diǎn),添加到尾部 elseloTail.next = e; //添加后,將loTail指向鏈表尾部,以便下次從尾部添加 loTail = e;}// 原位置+舊容量else { //hiTail處為null,就直接點(diǎn)添加到該位置 if (hiTail == null)hiHead = e; //hiTail為鏈表尾結(jié)點(diǎn),尾插法添加 elsehiTail.next = e; hiTail = e;}

​我們直接通過一個簡單的圖來理解吧

深入理解Java中的HashMap

6.4、resize總結(jié)

​resize代碼稍微長了點(diǎn),但是總結(jié)下來就是這幾點(diǎn)

判斷當(dāng)前oldTab長度是否為空,如果為空,則進(jìn)行初始化桶數(shù)組,也就回答了無參構(gòu)造函數(shù)初始化為什么沒有對容量和閾值進(jìn)行賦值,如果不為空,則進(jìn)行位運(yùn)算,左移一位,2倍運(yùn)算擴(kuò)容。擴(kuò)容,創(chuàng)建一個新容量的數(shù)組,遍歷舊的數(shù)組:如果節(jié)點(diǎn)為空,直接賦值插入如果節(jié)點(diǎn)為紅黑樹,則需要進(jìn)行進(jìn)行拆分操作(個人對紅黑樹還沒有理解,所以先不說明)如果為鏈表,根據(jù)hash算法進(jìn)行重新計(jì)算下標(biāo),將鏈表進(jìn)行拆分分組(相信看到這里基本上也知道鏈表拆分的大致過程了)

七、HashMap的get方法分析7.1、get方法源碼

​基本邏輯就是根據(jù)key算出hash值定位到哈希桶的索引,當(dāng)可以就是當(dāng)前索引的值則直接返回其對于的value,反之用key去遍歷equal該索引下的key,直到找到位置。

public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value;}final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //計(jì)算存放在數(shù)組table中的位置.具體計(jì)算方法上面也已經(jīng)介紹了 if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {//先查找是不是就是數(shù)組中的元素if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first;//該位置為紅黑樹根節(jié)點(diǎn)或者鏈表頭結(jié)點(diǎn)if ((e = first.next) != null) { //如果first為紅黑樹結(jié)點(diǎn),就在紅黑樹中遍歷查找 if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key); //不是樹結(jié)點(diǎn),就在鏈表中遍歷查找 do {if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null);} } return null;}

以上就是深入理解Java中的HashMap的詳細(xì)內(nèi)容,更多關(guān)于Java HashMap的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 成人国产网站 | 国产女人精品性视频 | 日本无卡无吗在线 | 丝袜国产 | 亚洲欧美日韩色 | 日韩做爰视频免费 | 国产精品麻豆高清在线观看 | 亚洲欧美激情综合首页 | 在线视频 一区二区 | 午夜a爱 | 国产精品怕怕怕视频免费 | 日韩a在线播放 | 久久99精品久久久久久久野外 | 综合欧美日韩 | 精品视频在线观看你懂的一区 | 91madou传媒在线观看 | 色精品一区二区三区 | 国产成人综合久久 | 国产精品黄页网站在线播放免费 | 日韩精品欧美国产精品亚 | 毛片直接看 | 欧美视频在线一区二区三区 | 国产国产精品人在线观看 | 国产免费看网站v片不遮挡 国产免费高清 | 一级特黄特交牲大片 | 一级毛片真人不卡免费播 | 麻豆自拍 | 97精品国产91久久久久久 | 国产一级特黄a大片免费 | 精品国产日韩亚洲一区91 | 一区二区三区欧美 | 美国毛片毛片全部免费 | 国产精品一区二区不卡 | 国产精品久久久久毛片真精品 | 亚洲免费在线看 | 精品国产九九 | 欧美在线视频网站 | 国产精品乱码免费一区二区 | 青草青草伊人精品视频 | 国产欧美日韩在线观看精品 | 1769国产精品一区2区 |