日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Java:從Map到HashMap的一步步實現(xiàn)!

一、 Map

成都創(chuàng)新互聯(lián)公司專注于江干企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站開發(fā),成都商城網(wǎng)站開發(fā)。江干網(wǎng)站建設(shè)公司,為江干等地區(qū)提供建站服務(wù)。全流程按需求定制制作,專業(yè)設(shè)計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)

1.1 Map 接口

在 Java 中, Map 提供了鍵——值的映射關(guān)系。映射不能包含重復(fù)的鍵,并且每個鍵只能映射到一個值。

以 Map 鍵——值映射為基礎(chǔ),java.util 提供了 HashMap(最常用)、 TreeMap、Hashtble、LinkedHashMap 等數(shù)據(jù)結(jié)構(gòu)。

衍生的幾種 Map 的主要特點:

  •  HashMap:最常用的數(shù)據(jù)結(jié)構(gòu)。鍵和值之間通過 Hash函數(shù) 來實現(xiàn)映射關(guān)系。當(dāng)進行遍歷的 key 是無序的
  •  TreeMap:使用紅黑樹構(gòu)建的數(shù)據(jù)結(jié)構(gòu),因為紅黑樹的原理,可以很自然的對 key 進行排序,所以 TreeMap 的 key 遍歷時是默認按照自然順序(升序)排列的。
  •  LinkedHashMap: 保存了插入的順序。遍歷得到的記錄是按照插入順序的。

1.2 Hash 散列函數(shù)

Hash (散列函數(shù))是把任意長度的輸入通過散列算法變換成固定長度的輸出。Hash 函數(shù)的返回值也稱為 哈希值 哈希碼 摘要或哈希。Hash作用如下圖所示:

Hash 函數(shù)可以通過選取適當(dāng)?shù)暮瘮?shù),可以在時間和空間上取得較好平衡。

解決 Hash 的兩種方式:拉鏈法和線性探測法

1.3 鍵值關(guān)系的實現(xiàn)

 
 
 
 
  1. interface Entry 

在 HashMap 中基于鏈表的實現(xiàn)

 
 
 
 
  1. static class Node implements Map.Entry {  
  2.         final int hash;  
  3.         final K key;  
  4.         V value;  
  5.         Node next;  
  6.         Node(int hash, K key, V value, Node next) {  
  7.             this.hash = hash;  
  8.             this.key = key;  
  9.             this.value = value;  
  10.             this.next = next;  
  11.         } 

用樹的方式實現(xiàn):

 
 
 
 
  1. static final class TreeNode extends LinkedHashMap.Entry {  
  2.         TreeNode parent;  // red-black tree links  
  3.         TreeNode left;  
  4.         TreeNode right;  
  5.         TreeNode prev;    // needed to unlink next upon deletion  
  6.         boolean red;  
  7.         TreeNode(int hash, K key, V val, Node next) {  
  8.             super(hash, key, val, next);  
  9.         } 

1.4 Map 約定的 API

1.4.1 Map 中約定的基礎(chǔ) API

基礎(chǔ)的增刪改查:

 
 
 
 
  1. int size();  // 返回大小  
  2. boolean isEmpty(); // 是否為空  
  3. boolean containsKey(Object key); // 是否包含某個鍵  
  4. boolean containsValue(Object value); // 是否包含某個值  
  5. V get(Object key); // 獲取某個鍵對應(yīng)的值   
  6. V put(K key, V value); // 存入的數(shù)據(jù)   
  7. V remove(Object key); // 移除某個鍵  
  8. void putAll(Map m); //將將另一個集插入該集合中  
  9. void clear();  // 清除  
  10. Set keySet(); //獲取 Map的所有的鍵返回為 Set集合  
  11. Collection values(); //將所有的值返回為 Collection 集合  
  12. Set> entrySet(); // 將鍵值對映射為 Map.Entry,內(nèi)部類 Entry 實現(xiàn)了映射關(guān)系的實現(xiàn)。并且返回所有鍵值映射為 Set 集合。   
  13. boolean equals(Object o);   
  14. int hashCode(); // 返回 Hash 值  
  15. default boolean replace(K key, V oldValue, V newValue); // 替代操作  
  16. default V replace(K key, V value); 

1.4.2 Map 約定的較為高級的 API

 
 
 
 
  1. default V getOrDefault(Object key, V defaultValue); //當(dāng)獲取失敗時,用 defaultValue 替代。 
  2. default void forEach(BiConsumer action)  // 可用 lambda 表達式進行更快捷的遍歷  
  3. default void replaceAll(BiFunction function);   
  4. default V putIfAbsent(K key, V value);  
  5. default V computeIfAbsent(K key,  
  6.             Function mappingFunction); 
  7. default V computeIfPresent(K key,  
  8.             BiFunction remappingFunction);  
  9. default V compute(K key,  
  10.             BiFunction remappingFunction)  
  11. default V merge(K key, V value,  
  12.             BiFunction remappingFunction)    

1.4.3 Map 高級 API 的使用

  •  getOrDefault() 當(dāng)這個通過 key獲取值,對應(yīng)的 key 或者值不存在時返回默認值,避免在使用過程中 null 出現(xiàn),避免程序異常。
  •  ForEach() 傳入 BiConsumer 函數(shù)式接口,表達的含義其實和 Consumer 一樣,都 accept 擁有方法,只是 BiConsumer 多了一個 andThen() 方法,接收一個BiConsumer接口,先執(zhí)行本接口的,再執(zhí)行傳入的參數(shù)的 accept 方法。   
 
 
 
 
  1. Map map = new HashMap<>();  
  2.      map.put("a", "1");  
  3.      map.put("b", "2");  
  4.      map.put("c", "3");  
  5.      map.put("d", "4");  
  6.      map.forEach((k, v) -> {  
  7.          System.out.println(k+"-"+v);  
  8.      });  
  9.  } 

更多的函數(shù)用法:

https://www.cnblogs.com/king0/p/runoob.com/java/java-hashmap.html

1.5 從 Map 走向 HashMap

HashMap 是 Map的一個實現(xiàn)類,也是 Map 最常用的實現(xiàn)類。

1.5.1 HashMap 的繼承關(guān)系

 
 
 
 
  1. public class HashMap extends AbstractMap  
  2.     implements Map, Cloneable, Serializable  

在 HashMap 的實現(xiàn)過程中,解決 Hash沖突的方法是拉鏈法。因此從原理來說 HashMap 的實現(xiàn)就是 數(shù)組 + 鏈表(數(shù)組保存鏈表的入口)。當(dāng)鏈表過長,為了優(yōu)化查詢速率,HashMap 將鏈表轉(zhuǎn)化為紅黑樹(數(shù)組保存樹的根節(jié)點),使得查詢速率為 log(n),而不是鏈表的 O(n)。

二、HashMap

 
 
 
 
  1. /*  
  2.  * @author  Doug Lea  
  3.  * @author  Josh Bloch  
  4.  * @author  Arthur van Hoff  
  5.  * @author  Neal Gafter  
  6.  * @see     Object#hashCode()  
  7.  * @see     Collection  
  8.  * @see     Map  
  9.  * @see     TreeMap  
  10.  * @see     Hashtable  
  11.  * @since   1.2  
  12.  */ 

首先 HashMap 由 Doug Lea 和 Josh Bloch 兩位大師的參與。同時 Java 的 Collections 集合體系,并發(fā)框架 Doug Lea 也做出了不少貢獻。

2.1 基本原理

對于一個插入操作,首先將鍵通過 Hash 函數(shù)轉(zhuǎn)化為數(shù)組的下標(biāo)。若該數(shù)組為空,直接創(chuàng)建節(jié)點放入數(shù)組中。若該數(shù)組下標(biāo)存在節(jié)點,即 Hash 沖突,使用拉鏈法,生成一個鏈表插入。

引用圖片來自 https://blog.csdn.net/woshimaxiao1/article/details/83661464

如果存在 Hash 沖突,使用拉鏈法插入,我們可以在這個鏈表的頭部插入,也可以在鏈表的尾部插入,所以在 JDK 1.7 中使用了頭部插入的方法,JDK 1.8 后續(xù)的版本中使用尾插法。

JDK 1.7 使用頭部插入的可能依據(jù)是最近插入的數(shù)據(jù)是最常用的,但是頭插法帶來的問題之一,在多線程會鏈表的復(fù)制會出現(xiàn)死循環(huán)。所以 JDK 1.8 之后采用的尾部插入的方法。關(guān)于這點,可以看:Java8之后,HashMap鏈表插入方式->為何要從頭插入改為尾插入

在 HashMap 中,前面說到的 數(shù)組+鏈表 的數(shù)組的定義

 
 
 
 
  1. transient Node[] table; 

鏈表的定義:

 
 
 
 
  1. static class Node implements Map.Entry  

2.1.2 提供的構(gòu)造函數(shù)

 
 
 
 
  1. public HashMap() { // 空參  
  2.       this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted  
  3.   }  
  4.   public HashMap(int initialCapacity) { //帶有初始大小的,一般情況下,我們需要規(guī)劃好 HashMap 使用的大小,因為對于一次擴容操作,代價是非常的大的  
  5.       this(initialCapacity, DEFAULT_LOAD_FACTOR);  
  6.   }  
  7.   public HashMap(int initialCapacity, float loadFactor); // 可以自定義負載因子  public HashMap(int initialCapacity, float loadFactor); // 可以自定義負載因子 

三個構(gòu)造函數(shù),都沒有完全的初始化 HashMap,當(dāng)我們第一次插入數(shù)據(jù)時,才進行堆內(nèi)存的分配,這樣提高了代碼的響應(yīng)速度。

2.2 HashMap 中的 Hash函數(shù)定義

 
 
 
 
  1. static final int hash(Object key) {  
  2.         int h;  
  3.         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 將 h 高 16 位和低 16 位 進行異或操作。  
  4.     }  
  5. // 采用 異或的原因:兩個進行位運算,在與或異或中只有異或到的 0 和 1 的概率是相同的,而&和|都會使得結(jié)果偏向0或者1。 

這里可以看到,Map 的鍵可以為 null,且 hash 是一個特定的值 0。

Hash 的目的是獲取數(shù)組 table 的下標(biāo)。Hash 函數(shù)的目標(biāo)就是將數(shù)據(jù)均勻的分布在 table 中。

讓我們先看看如何通過 hash 值得到對應(yīng)的數(shù)組下標(biāo)。第一種方法:hash%table.length()。但是除法操作在 CPU 中執(zhí)行比加法、減法、乘法慢的多,效率低下。第二種方法 table[(table.length - 1) & hash] 一個與操作一個減法,仍然比除法快。這里的約束條件為  table.length = 2^N。

 
 
 
 
  1. table.length =16  
  2. table.length -1 = 15 1111 1111  
  3. //任何一個數(shù)與之與操作,獲取到這個數(shù)的低 8 位,其他位為 0 

上面的例子可以讓我們獲取到對應(yīng)的下標(biāo),而 (h = key.hashCode()) ^ (h >>> 16) 讓高 16 也參與運算,讓數(shù)據(jù)充分利用,一般情況下 table 的索引不會超過 216,所以高位的信息我們就直接拋棄了,^ (h >>> 16) 讓我們在數(shù)據(jù)量較少的情況下,也可以使用高位的信息。如果 table 的索引超過 216, hashCode() 的高 16 為 和 16 個 0 做異或得到的 Hash 也是公平的。

2.3 HashMap 的插入操作

上面我們已經(jīng)知道如果通過 Hash 獲取到 對應(yīng)的 table 下標(biāo),因此我們將對應(yīng)的節(jié)點加入到鏈表就完成了一個 Map 的映射,的確 JDK1.7 中的 HashMap 實現(xiàn)就是這樣。讓我們看一看 JDK 為實現(xiàn)現(xiàn)實的 put 操作。

定位到 put() 操作。

 
 
 
 
  1. public V put(K key, V value) {  
  2.        return putVal(hash(key), key, value, false, true);  
  3.    } 

可以看到 put 操作交給了 putVal 來進行通用的實現(xiàn)。

 
 
 
 
  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict);  
  2. //onlyIfAbsent  如果當(dāng)前位置已存在一個值,是否替換,false是替換,true是不替換  
  3. evict // 鉤子函數(shù)的參數(shù),LinkedHashMap 中使用到,HashMap 中無意義。 

2.3.1 putVal 的流程分析

其實 putVal() 流程的函數(shù)非常的明了。這里挑了幾個關(guān)鍵步驟來引導(dǎo)。

是否第一次插入,true 調(diào)用 resizer() 進行調(diào)整,其實此時 resizer() 是進行完整的初始化,之后直接賦值給對應(yīng)索引的位置。

 
 
 
 
  1. if ((tab = table) == null || (n = tab.length) == 0) // 第一次 put 操作, tab 沒有分配內(nèi)存,通過 redize() 方法分配內(nèi)存,開始工作。  
  2.            n = (tab = resize()).length;  
  3.        if ((p = tab[i = (n - 1) & hash]) == null)  
  4.            tab[i] = newNode(hash, key, value, null); 

如果鏈表已經(jīng)轉(zhuǎn)化為樹,則使用樹的插入。

 
 
 
 
  1. else if (p instanceof TreeNode)  
  2.                 e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); 

用遍歷的方式遍歷每個 Node,如果遇到鍵相同,或者到達尾節(jié)點的next 指針將數(shù)據(jù)插入,記錄節(jié)點位置退出循環(huán)。若插入后鏈表長度為 8 則調(diào)用 treeifyBin() 是否進行樹的轉(zhuǎn)化 。

 
 
 
 
  1. for (int binCount = 0; ; ++binCount) {  
  2.                    if ((e = p.next) == null) {  
  3.                        p.next = newNode(hash, key, value, null);  
  4.                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  
  5.                            treeifyBin(tab, hash);  
  6.                        break;  
  7.                    }  
  8.                    if (e.hash == hash &&  
  9.                        ((k = e.key) == key || (key != null && key.equals(k))))  
  10.                        break;  
  11.                    p = e; 
  12.                 } 

對鍵重復(fù)的操作:更新后返回舊值,同時還取決于onlyIfAbsent,普通操作中一般為 true,可以忽略。 

 
 
 
 
  1. if (e != null) { // existing mapping for key  
  2.               V oldValue = e.value; 
  3.                if (!onlyIfAbsent || oldValue == null)  
  4.                   e.value = value;  
  5.               afterNodeAccess(e); //鉤子函數(shù),進行后續(xù)其他操作,HashMap中為空,無任何操作。  
  6.               return oldValue;  
  7.           } 

~

 
 
 
 
  1. ++modCount;  
  2.    if (++size > threshold)  
  3.        resize();  
  4.    afterNodeInsertion(evict);  
  5.    return null; 

后續(xù)的數(shù)據(jù)維護。

2.3.2 modCount 的含義

fail-fast 機制是java集合(Collection)中的一種錯誤機制。當(dāng)多個線程對同一個集合的內(nèi)容進行操作時,就可能會產(chǎn)生fail-fast事件。例如:當(dāng)某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內(nèi)容被其他線程所改變了;那么線程A訪問集合時,就會拋出ConcurrentModificationException異常,產(chǎn)生fail-fast事件。一種多線程錯誤檢查的方式,減少異常的發(fā)生。

一般情況下,多線程環(huán)境 我們使用 ConcurrentHashMap 來代替 HashMap。

2.4 resize() 函數(shù)

HashMap 擴容的特點:默認的table 表的大小事 16,threshold 為 12。負載因子 loadFactor .75,這些都是可以構(gòu)造是更改。以后擴容都是 2 倍的方式增加。

至于為何是0.75 代碼的注釋中也寫了原因,對 Hash函數(shù)構(gòu)建了泊松分布模型,進行了分析。

2.4.1 HashMap 預(yù)定義的一些參數(shù)

 
 
 
 
  1. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16  HashMap 的默認大小。 為什么使用 1 <<4  
  2. static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量  
  3. static final float DEFAULT_LOAD_FACTOR = 0.75f; // 加載因子,擴容使用  
  4. static final int UNTREEIFY_THRESHOLD = 6;//  樹結(jié)構(gòu)轉(zhuǎn)化為鏈表的閾值  
  5. static final int TREEIFY_THRESHOLD = 8;  //  鏈表轉(zhuǎn)化為樹結(jié)構(gòu)的閾值  
  6. static final int MIN_TREEIFY_CAPACITY = 64; // 鏈表轉(zhuǎn)變成樹之前,還會有一次判斷,只有數(shù)組長度大于 64 才會發(fā)生轉(zhuǎn)換。這是為了避免在哈希表建立初期,多個鍵值對恰好被放入了同一個鏈表中而導(dǎo)致不必要的轉(zhuǎn)化。 
  7. // 定義的有關(guān)變量  
  8. int threshold;   // threshold表示當(dāng)HashMap的size大于threshold時會執(zhí)行resize操作 

這些變量都是和 HashMap 的擴容機制有關(guān),將會在下文中用到。

2.4.2 resize() 方法解析 

 
 
 
 
  1. Node[] oldTab = table;  
  2.      int oldCap = (oldTab == null) ? 0 : oldTab.length;   
  3.      int oldThr = threshold;  
  4.      int newCap, newThr = 0; // 定義了 舊表長度、舊表閾值、新表長度、新表閾值  
 
 
 
 
  1. if (oldCap > 0) {  // 插入過數(shù)據(jù),參數(shù)不是初始化的  
  2.           if (oldCap >= MAXIMUM_CAPACITY) {  // 如果舊的表長度大于 1 << 30;  
  3.               threshold = Integer.MAX_VALUE; // threshold 設(shè)置 Integer 的最大值。也就是說我們可以插入 Integer.MAX_VALUE 個數(shù)據(jù)  
  4.               return oldTab; // 直接返回舊表的長度,因為表的下標(biāo)索引無法擴大了。   
  5.           }  
  6.           else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //   
  7.                    oldCap >= DEFAULT_INITIAL_CAPACITY)  //新表的長度為舊表的長度的 2 倍。 
  8.               newThr = oldThr << 1; // double threshold 新表的閾值為同時為舊表的兩倍  
  9.       }  
  10.       else if (oldThr > 0) //   public HashMap(int initialCapacity, float loadFactor)   中的  this.threshold = tableSizeFor(initialCapacity);  給正確的位置    
  11.           newCap = oldThr;  
  12.       else {               // zero initial threshold signifies using defaults ,如果調(diào)用了其他兩個構(gòu)造函數(shù),則下面代碼初始化。因為他們都沒有對其 threshold 設(shè)置,默認為 0, 
  13.           newCap = DEFAULT_INITIAL_CAPACITY;  
  14.           newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  
  15.       }  
  16.       if (newThr == 0) { // 修正 threshold,例如上面的   else if (oldThr > 0)  部分就沒有設(shè)置。  
  17.           float ft = (float)newCap * loadFactor;  
  18.           newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?  
  19.                     (int)ft : Integer.MAX_VALUE);  
  20.       }  
  21.       threshold = newThr;  
  22.       @SuppressWarnings({"rawtypes","unchecked"}) 

當(dāng)一些參數(shù)設(shè)置正確后便開始擴容。 

 
 
 
 
  1. Node[] newTab = (Node[])new Node[newCap];  

當(dāng)擴容完畢之后,自然就是將原表中的數(shù)據(jù)搬到新的表中。下面代碼完成了該任務(wù)。

 
 
 
 
  1. if (oldTab != null) {  
  2.    for (int j = 0; j < oldCap; ++j) {  
  3.       ....   
  4.    }  

如何正確的,快速的擴容調(diào)整每個鍵值節(jié)點對應(yīng)的下標(biāo)?第一種方法:遍歷節(jié)點再使用 put() 加入一遍,這種方法實現(xiàn),但是效率低下。第二種,我們手動組裝好鏈表,加入到相應(yīng)的位置。顯然第二種比第一種高效,因為第一種 put() 還存在其他不屬于這種情況的判斷,例如重復(fù)鍵的判斷等。

練手項目,學(xué)習(xí)強化,點擊這里

所以 JDK 1.8 也使用了第二種方法。我們可以繼續(xù)使用e.hash & (newCap - 1)找到對應(yīng)的下標(biāo)位置,對于舊的鏈表,執(zhí)行e.hash & (newCap - 1) 操作,只能產(chǎn)生兩個不同的索引。一個保持原來的索引不變,另一個變?yōu)?原來索引 + oldCap(因為 newCap 的加入產(chǎn)生導(dǎo)致索引的位數(shù)多了 1 位,即就是最左邊的一個,且該位此時結(jié)果為 1,所以相當(dāng)于 原來索引 + oldCap)。所以可以使用 if ((e.hash & oldCap) == 0) 來確定出索引是否來變化。

因此這樣我們就可以將原來的鏈表拆分為兩個新的鏈表,然后加入到對應(yīng)的位置。為了高效,我們手動的組裝好鏈表再存儲到相應(yīng)的下標(biāo)位置上。

 
 
 
 
  1. oldCap  = 16  
  2. newCap  = 32  
  3. hash       : 0001 1011  
  4. oldCap-1   : 0000 1111  
  5. 結(jié)果為     :  0000 1011  對應(yīng)的索引的 11  
  6. -------------------------  
  7. e.hash & oldCap 則定于 1,則需要進行調(diào)整索引  
  8. oldCap  = 16  
  9. hash       : 0001 1011   
  10. newCap-1   : 0001 1111  
  11. 結(jié)果為     :  0001 1011  
  12. 相當(dāng)于 1011 + 1 0000 原來索引 + newCap 
 
 
 
 
  1. for (int j = 0; j < oldCap; ++j)  // 處理每個鏈表  

特殊條件處理

 
 
 
 
  1. Node e;  
  2.                 if ((e = oldTab[j]) != null) {  
  3.                     oldTab[j] = null;  
  4.                     if (e.next == null)  // 該 鏈表只有一個節(jié)點,那么直接復(fù)制到對應(yīng)的位置,下標(biāo)由 e.hash & (newCap - 1) 確定  
  5.                         newTab[e.hash & (newCap - 1)] = e;  
  6.                     else if (e instanceof TreeNode) // 若是 樹,該給樹的處理程序  
  7.                         ((TreeNode)e).split(this, newTab, j, oldCap); 

普通情況處理:     

 
 
 
 
  1. else { // preserve order  
  2.                         Node loHead = null, loTail = null;  // 構(gòu)建原來索引位置 的鏈表,需要的指針  
  3.                         Node hiHead = null, hiTail = null; // 構(gòu)建 原來索引 + oldCap 位置 的鏈表需要的指針  
  4.                         Node next;  
  5.                         do {  
  6.                             next = e.next;  
  7.                             if ((e.hash & oldCap) == 0) {  
  8.                                 if (loTail == null)  
  9.                                     loHead = e;  
  10.                                 else  
  11.                                     loTail.next = e;  
  12.                                 loTail = e;  
  13.                             }  
  14.                             else {  
  15.                                 if (hiTail == null) 
  16.                                      hiHead = e;  
  17.                                 else  
  18.                                     hiTail.next = e;  
  19.                                 hiTail = e;  
  20.                             }  
  21.                         } while ((e = next) != null); // 將原來的鏈表劃分兩個鏈表  
  22.                         if (loTail != null) { // 將鏈表寫入到相應(yīng)的位置 
  23.                             loTail.next = null;  
  24.                             newTab[j] = loHead; 
  25.                          }  
  26.                         if (hiTail != null) {  
  27.                             hiTail.next = null;  
  28.                             newTab[j + oldCap] = hiHead;  
  29.                         }  
  30.                     } 

到此 resize() 方法的邏輯完成了。總的來說 resizer() 完成了 HashMap 完整的初始化,分配內(nèi)存和后續(xù)的擴容維護工作。

2.5 remove 解析 

 
 
 
 
  1. public V remove(Object key) {  
  2.        Node e;  
  3.        return (e = removeNode(hash(key), key, null, false, true)) == null ?  
  4.            null : e.value;  
  5.    } 

將 remove 刪除工作交給內(nèi)部函數(shù) removeNode() 來實現(xiàn)。

 
 
 
 
  1. final Node removeNode(int hash, Object key, Object value,  
  2.                                boolean matchValue, boolean movable) {  
  3.         Node[] tab; Node p; int n, index;  
  4.         if ((tab = table) != null && (n = tab.length) > 0 &&  
  5.             (p = tab[index = (n - 1) & hash]) != null) {  // 獲取索引, 
  6.             Node node = null, e; K k; V v;  
  7.             if (p.hash == hash &&    
  8.                 ((k = p.key) == key || (key != null && key.equals(k)))) // 判斷索引處的值是不是想要的結(jié)果  
  9.                 node = p;    
  10.             else if ((e = p.next) != null) { // 交給樹的查找算法  
  11.                 if (p instanceof TreeNode)  
  12.                     node = ((TreeNode)p).getTreeNode(hash, key);  
  13.                 else {  
  14.                     do { // 遍歷查找  
  15.                         if (e.hash == hash &&  
  16.                             ((k = e.key) == key ||  
  17.                              (key != null && key.equals(k)))) {  
  18.                             node = e;  
  19.                             break;  
  20.                         } 
  21.                          p = e;  
  22.                     } while ((ee = e.next) != null);  
  23.                 }  
  24.             }  
  25.             if (node != null && (!matchValue || (v = node.value) == value ||  
  26.                                  (value != null && value.equals(v)))) {  
  27.                 if (node instanceof TreeNode)  //樹的刪除  
  28.                     ((TreeNode)node).removeTreeNode(this, tab, movable);  
  29.                 else if (node == p) // 修復(fù)鏈表,鏈表的刪除操作  
  30.                     tab[index] = node.next;   
  31.                 else  
  32.                     p.next = node.next;    
  33.                 ++modCount;  
  34.                 --size;  
  35.                 afterNodeRemoval(node); 
  36.                  return node;  
  37.             }  
  38.         }  
  39.         return&
    新聞名稱:Java:從Map到HashMap的一步步實現(xiàn)!
    文章路徑:http://m.5511xx.com/article/dhdghde.html