JAVA數據結構——Map之HashMap

JAVA數據結構——Map之HashMap

 

1、原型及簡介

  原型:public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializablenode

  簡介:HashMap基於散列表實現的一個key-value數據結構,可以實現經過key值快速查找。HashMap繼承自AbstractMap抽閒類,實現了Map接口。算法

 2、數據結構原理介紹

  以下圖所示,HashMap是利用數組與鏈表結合的形式構建的。豎列爲數組結構,默認初始數量爲16(1<<4)個,橫列爲鏈表結構用於解決散列衝突的問題。當數組中有值得元素超過了裝載因子的比例(默認爲0.75)時,會引起擴容的操做。此操做是爲了不元素過滿時引發的鏈表長度過長,從而影響查找性能。bootstrap

  這裏寫圖片描述

上圖爲jdk1.7以前的實現,jdk1.8實現方法是當某一個桶中的元素個數超過了8時,將此桶中的鏈表構建成紅黑樹。數組

3、經常使用源碼解析

  一、常量說明數據結構

 1     /**
 2      * 默認初始容量
 3      */
 4     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 5 
 6     /**
 7      * 最大元素數量
 8      */
 9     static final int MAXIMUM_CAPACITY = 1 << 30;
10 
11     /**
12      * 默認裝載因子
13      */
14     static final float DEFAULT_LOAD_FACTOR = 0.75f;
15 
16     /**
17      * 當一個桶中的元素的數量大於8時,該鏈表結構可能被轉化成一棵紅黑樹,優化查找
18      */
19     static final int TREEIFY_THRESHOLD = 8;
20 
21     /**
22      * 當一個桶中的元素的數量小於6時,該樹結構被轉化成鏈表。
23      */
24     static final int UNTREEIFY_THRESHOLD = 6;
25 
26     /**
27      * 桶被樹化的另外一個條件是,當hashmap中元素個數大於4 * MIN_TREEIFY_CAPACITY 。避免調整大小和treei閾值之間的衝突。
28      */
29     static final int MIN_TREEIFY_CAPACITY = 64;
常量值說明

  二、變量說明app

 1     /**
 2      * The table, initialized on first use, and resized as
 3      * necessary. When allocated, length is always a power of two.
 4      * (We also tolerate length zero in some operations to allow
 5      * bootstrapping mechanics that are currently not needed.)
 6      */
 7     transient Node<K,V>[] table;
 8 
 9     /**
10      * Holds cached entrySet(). Note that AbstractMap fields are used
11      * for keySet() and values().
12      */
13     transient Set<Map.Entry<K,V>> entrySet;
14 
15     /**
16      * HashMap中當前元素個數
17      */
18     transient int size;
19 
20     /**
21      * HashMap對象被修改次數,防止出現多個線程修改出現的線程不一致性,每次修改HashMap的值時,都會自增。當使用Iterator操做HashMap時,會用此值與Iterator內部的值作一次比較,從而判斷HashMap有沒有被其餘線程修改。故建議每次遍歷HashMap時都使用Iterator。
22      */
23     transient int modCount;
24 
25     /**
26      * 裝載因子
27      */
28     final float loadFactor;
變量值說明

  漏了一個變量:threshold,表明着擴容的閾值,其值爲  當前容量*裝載因子ide

  三、節點數據結構函數

 1     static class Node<K,V> implements Map.Entry<K,V> {
 2         final int hash; //散列碼
 3         final K key;  //key值
 4         V value;  //value值
 5         Node<K,V> next;  //鏈表結構指針,指向下一節點
 6 
 7         Node(int hash, K key, V value, Node<K,V> next) {
 8             this.hash = hash;
 9             this.key = key;
10             this.value = value;
11             this.next = next;
12         }
13 
14         public final K getKey()        { return key; }
15         public final V getValue()      { return value; }
16         public final String toString() { return key + "=" + value; }
17 
18         // 返回該節點的散列碼
19         public final int hashCode() {
20             // key值的散列碼 冪運算 value值得散列碼
21             // 散列函數爲空值返回0,非空值則返回該對象的32位JVM地址
22             return Objects.hashCode(key) ^ Objects.hashCode(value);
23         }
24 
25         public final V setValue(V newValue) {
26             V oldValue = value;
27             value = newValue;
28             return oldValue;
29         }
30 
31         public final boolean equals(Object o) {
32             if (o == this)
33                 return true;
34             if (o instanceof Map.Entry) {
35                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
36                 if (Objects.equals(key, e.getKey()) &&
37                     Objects.equals(value, e.getValue()))
38                     return true;
39             }
40             return false;
41         }
42     }
節點數據結構

  四、經常使用方法性能

 

 1     /**
 2      * 指定初始大小以及裝載因子
 3      */
 4     public HashMap(int initialCapacity, float loadFactor) {
 5         if (initialCapacity < 0)
 6             throw new IllegalArgumentException("Illegal initial capacity: " +
 7                                                initialCapacity);
 8         if (initialCapacity > MAXIMUM_CAPACITY)
 9             initialCapacity = MAXIMUM_CAPACITY;
10         if (loadFactor <= 0 || Float.isNaN(loadFactor))
11             throw new IllegalArgumentException("Illegal load factor: " +
12                                                loadFactor);
13         this.loadFactor = loadFactor;
14         this.threshold = tableSizeFor(initialCapacity);
15     }
16 
17     /**
18      * 返回一個比cap大的最小的2的冪次方整數
19      */
20     static final int tableSizeFor(int cap) {
21         int n = cap - 1;
22         n |= n >>> 1;
23         n |= n >>> 2;
24         n |= n >>> 4;
25         n |= n >>> 8;
26         n |= n >>> 16;
27         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
28     }
構造方法

由於HashMap的容量必須是2的冪次方,因此構造方法中有關於tableSizeFor方法,得到比給定容量大的最小的2的冪次方整數,很霸氣的算法,其具體的說明可參考連接:優化

【轉載】http://blog.csdn.net/fan2012huan/article/details/51097331(寫的很詳細,很好)。

 

  1     /**
  2      * 將key-value鍵值對放入HashMap中
  3      */
  4     public V put(K key, V value) {
  5         return putVal(hash(key), key, value, false, true);
  6     }
  7 
  8     /**
  9      * 實際put的方法
 10      */
 11     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 12                    boolean evict) {
 13         Node<K,V>[] tab; //暫存HashMap節點數組
 14         Node<K,V> p; //暫存本次要插入的節點元素數據
 15         int n, i;
 16 
 17         //若是當前HashMap爲空,則計算新分配空間
 18         if ((tab = table) == null || (n = tab.length) == 0)
 19             n = (tab = resize()).length;
 20         // 若是計算出的新節點位置(hash & (n - 1) 等價於 hash % n)是空,則將元素直接放入
 21         if ((p = tab[i = (n - 1) & hash]) == null)
 22             tab[i] = newNode(hash, key, value, null);
 23         //插入新的節點,並從新組織HashMap(肯定位置並決定是否樹化)
 24         else {
 25             Node<K,V> e; K k;
 26             // 插入了重複的key值(hash碼一致且key值一致)
 27             if (p.hash == hash &&
 28                 ((k = p.key) == key || (key != null && key.equals(k))))
 29                 e = p;
 30             // 若是p是紅黑樹,則執行紅黑樹的插入操做
 31             else if (p instanceof TreeNode)
 32                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 33             // 此分支表明了鏈表的插入操做
 34             else {
 35                 for (int binCount = 0; ; ++binCount) {
 36                     // 到達鏈表尾端,則執行插入
 37                     if ((e = p.next) == null) {
 38                         //插入
 39                         p.next = newNode(hash, key, value, null);
 40                         若是節點數量超過閾值,則執行樹化操做
 41                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 42                             treeifyBin(tab, hash);
 43                         break;
 44                     }
 45                     // 插入了重複值
 46                     if (e.hash == hash &&
 47                         ((k = e.key) == key || (key != null && key.equals(k))))
 48                         break;
 49                     p = e;
 50                 }
 51             }
 52             if (e != null) { // existing mapping for key
 53                 V oldValue = e.value;
 54                 if (!onlyIfAbsent || oldValue == null)
 55                     e.value = value;
 56                 afterNodeAccess(e);
 57                 return oldValue;
 58             }
 59         }
 60         ++modCount;
 61         //若是元素個數超過閾值,則從新分配空間,並組織數據結構
 62         if (++size > threshold)
 63             resize();
 64         afterNodeInsertion(evict);
 65         return null;
 66     }
 67 
 68     /**
 69      * 針對每一個桶從新分配空間
 70      */
 71     final Node<K,V>[] resize() {
 72         Node<K,V>[] oldTab = table; //暫存當前table結構
 73         int oldCap = (oldTab == null) ? 0 : oldTab.length; //暫存當前桶的數量
 74         int oldThr = threshold; //暫存擴容的閾值
 75         int newCap, newThr = 0; //定義新的容量和閾值
 76         // 若是原有HashMap不爲空
 77         if (oldCap > 0) {
 78             //若是容量已經達到了上限,則不擴容,返回原oldTab
 79             if (oldCap >= MAXIMUM_CAPACITY) {
 80                 threshold = Integer.MAX_VALUE;
 81                 return oldTab;
 82             }
 83             //若是容量沒有達到上限,則將容量及擴容閾值均翻倍
 84             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
 85                      oldCap >= DEFAULT_INITIAL_CAPACITY)
 86                 newThr = oldThr << 1; // double threshold
 87         }
 88         // 容量爲0但老的閾值大於0,則閾值保持不變
 89         else if (oldThr > 0) // initial capacity was placed in threshold
 90             newCap = oldThr;
 91         // 若是容量與閾值均爲0,則執行初始化
 92         else {               // zero initial threshold signifies using defaults
 93             newCap = DEFAULT_INITIAL_CAPACITY;//容量爲默認容量
 94             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//閾值爲默認容量*默認閥值(16*0.75)
 95         }
 96         // 若是現有容量翻倍後大於最大容量或現有容量小於系統默認值(16),纔會出現新閾值=0的狀況,
 97         if (newThr == 0) {
 98             float ft = (float)newCap * loadFactor;
 99             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
100                       (int)ft : Integer.MAX_VALUE);
101         }
102 
103         // 設置最新的擴容閾值
104         threshold = newThr;
105 
106         // 建立擴容後的桶數組
107         @SuppressWarnings({"rawtypes","unchecked"})
108             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
109         table = newTab;
110 
111         //從新組織每一個桶內的鏈表或樹狀結構
112         if (oldTab != null) {
113             //遍歷每一個桶,分別處理每一個桶中的數據
114             for (int j = 0; j < oldCap; ++j) {
115                 Node<K,V> e;
116 
117                 //當前桶不爲空,則需將oldTab中的內容組織到newTab中
118                 if ((e = oldTab[j]) != null) {
119                     oldTab[j] = null;
120                     if (e.next == null) //e沒有子節點,則根據e的hash值直接將此節點放到擴容後的桶數組中合適位置
121                         // 此處e.hash & (newCap - 1)等價於e.hash % newCap
122                         newTab[e.hash & (newCap - 1)] = e;
123                     else if (e instanceof TreeNode) //若是e是個樹型節點,則遍歷紅黑樹,將樹中的每一個節點放到新的桶數組中合適的位置,並根據新的結構決定是否須要將每一個桶作樹化
124                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
125                     else { // e是個鏈式節點
126                         Node<K,V> loHead = null, loTail = null;
127                         Node<K,V> hiHead = null, hiTail = null;
128                         Node<K,V> next;
129                         do {
130                             next = e.next;
131                             // 由於容量擴大了二倍,則元素要麼保持不變,要麼放到index + oldCap位置
132                             if ((e.hash & oldCap) == 0) {// 元素位置保持不變,先將元素放到lo鏈表中
133                                 if (loTail == null)
134                                     loHead = e;
135                                 else
136                                     loTail.next = e;
137                                 loTail = e;
138                             }
139                             else {// 元素位置須要移動,先將元素放到hi鏈表中
140                                 if (hiTail == null)
141                                     hiHead = e;
142                                 else
143                                     hiTail.next = e;
144                                 hiTail = e;
145                             }
146                         } while ((e = next) != null);
147                         if (loTail != null) {//將lo鏈表放到newTab中原來(j)的位置
148                             loTail.next = null;
149                             newTab[j] = loHead;
150                         }
151                         if (hiTail != null) {//將hi鏈表放到newTab中擴容(j+oldCap)的位置
152                             hiTail.next = null;
153                             newTab[j + oldCap] = hiHead;
154                         }
155                     }
156                 }
157             }
158         }
159     
160         //返回最新的結構
161         return newTab;
162     }
public V put(K key, V value)

put方法包含了HashMap的實際初始化及構建的過程,仔細研究put方法,能夠更好的瞭解HashMap這種數據結構

 

 1     /**
 2      * 根據key值獲取value值
 3      */
 4     public V get(Object key) {
 5         Node<K,V> e;
 6         return (e = getNode(hash(key), key)) == null ? null : e.value;
 7     }
 8 
 9     /**
10      * 根據哈希碼及key值獲取value值
11      */
12     final Node<K,V> getNode(int hash, Object key) {
13         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
14 
15         //若是表不爲空且表的長度不爲空且根據hash碼定位到桶不爲空
16         if ((tab = table) != null && (n = tab.length) > 0 &&
17             (first = tab[(n - 1) & hash]) != null) {
18 
19             //若是該桶的第一個元素hash碼與傳參相同且key值也相同,則返回該元素節點
20             if (first.hash == hash && // always check first node
21                 ((k = first.key) == key || (key != null && key.equals(k))))
22                 return first;
23 
24             //若是該節點的下一個下一個節點不爲空
25             if ((e = first.next) != null) {
26                 //若是該節點是樹形節點,則遍歷紅黑樹查找匹配節點
27                 if (first instanceof TreeNode)
28                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
29 
30                 //若是是鏈式節點,則遍歷該鏈表查找匹配節點
31                 do {
32                     if (e.hash == hash &&
33                         ((k = e.key) == key || (key != null && key.equals(k))))
34                         return e;
35                 } while ((e = e.next) != null);
36             }
37         }
38         return null;
39     }
public V get(Object key)
相關文章
相關標籤/搜索