hashMap 源碼註釋分析(二)

接着上一篇繼續

  1     /**
  2      * 返回map的長度
  3      * @return the number of key-value mappings in this map
  4      */
  5     public int size() {
  6         return size;
  7     }
  8 
  9     /**
 10      *如果map的長度爲0 則返回true
 11      * @return <tt>true</tt> if this map contains no key-value mappings
 12      */
 13     public boolean isEmpty() {
 14         return size == 0;
 15     }
 16 
 17     /**
 18      * map的key 與value 都可以爲 null
 19      * @see #put(Object, Object)
 20      */
 21     public V get(Object key) {
 22         Node<K,V> e;
 23         return (e = getNode(hash(key), key)) == null ? null : e.value;
 24     }
 25 
 26     /**
 27      * Implements Map.get and related methods
 28      *通過key值的hash值 與key 去查找Node
 29      * @param hash hash for key
 30      * @param key the key
 31      * @return the node, or null if none
 32      */
 33     final Node<K,V> getNode(int hash, Object key) {
 34         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
 35         if ((tab = table) != null && (n = tab.length) > 0 &&
 36             (first = tab[(n - 1) & hash]) != null) {//將table 賦值給tab table不是 null ,table 長度大於0 該hash找到的hash桶Node不爲null
 37             if (first.hash == hash && // always check first node//如果是一個元素直接判斷
 38                 ((k = first.key) == key || (key != null && key.equals(k))))//判斷找到的桶的hash值是否等於當前要找的hash值,key值是否是自己要找的key
 39                 return first;
 40             if ((e = first.next) != null) {//判斷該哈希值是否有下一個元素
 41                 if (first instanceof TreeNode)//判斷該hash桶是否是紅黑樹
 42                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);//如果是紅黑樹已紅黑樹的方式查找
 43                 do {//不是紅黑樹就是鏈表,以鏈表的方式遍歷查找。
 44                     if (e.hash == hash &&
 45                         ((k = e.key) == key || (key != null && key.equals(k))))
 46                         return e;
 47                 } while ((e = e.next) != null);
 48             }
 49         }
 50         return null;
 51     }
 52 
 53     /**
 54      * 判斷是否包含某個key ,就是一個get 操作
 55      * @param   key   The key whose presence in this map is to be tested
 56      * @return <tt>true</tt> if this map contains a mapping for the specified
 57      * key.
 58      */
 59     public boolean containsKey(Object key) {
 60         return getNode(hash(key), key) != null;
 61     }
 62 
 63     /**
 64      * 將指定值與該映射中的指定鍵相關聯。
 65      * 如果該映射先前包含該鍵的映射,則舊
 66      * 值被替換。
 67      *
 68      * @param key key with which the specified value is to be associated
 69      * @param value value to be associated with the specified key
 70      * @return the previous value associated with <tt>key</tt>, or
 71      *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
 72      *         (A <tt>null</tt> return can also indicate that the map
 73      *         previously associated <tt>null</tt> with <tt>key</tt>.)
 74      */
 75     public V put(K key, V value) {
 76         return putVal(hash(key), key, value, false, true);
 77     }
 78 
 79     /**
 80      * Implements Map.put and related methods
 81      *
 82      * @param hash hash for key
 83      * @param key the key
 84      * @param value the value to put
 85      * @param onlyIfAbsent 如果 true, 不能修改覆蓋當前的值
 86      * @param evict 如果 false, 當前table正在創建
 87      * @return previous value, or null if none
 88      */
 89     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 90                    boolean evict) {
 91         Node<K,V>[] tab; Node<K,V> p; int n, i;
 92         if ((tab = table) == null || (n = tab.length) == 0)//如果table 沒有初始化
 93             n = (tab = resize()).length;//擴容操作
 94         if ((p = tab[i = (n - 1) & hash]) == null)//  (n - 1) & hash 映射到一個hash桶賦值給p  ,n的值爲2的冪 轉爲二進制就是1000..減1 後就是11111111...
 95             //這樣與hash進行& 操作時,就是111111111...與hash 之間 相同位置都爲1纔是1,這樣就可以均勻分佈到了hash桶中。
 96             tab[i] = newNode(hash, key, value, null);//創建第一個hash桶
 97         else {
 98             Node<K,V> e; K k;
 99             if (p.hash == hash &&
100                 ((k = p.key) == key || (key != null && key.equals(k))))//判斷p 這個hash桶的hash值是否==當前的key
101                 e = p;
102             else if (p instanceof TreeNode)//如果hash桶是紅黑樹
103                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//添加到樹
104             else {//是鏈表
105                 for (int binCount = 0; ; ++binCount) {
106                     if ((e = p.next) == null) {//循環判斷桶中是否有下一個元素
107                         p.next = newNode(hash, key, value, null);
108                         if (binCount >= TREEIFY_THRESHOLD - 1) //判斷是否達到轉換爲紅黑樹的閾值
109                             treeifyBin(tab, hash);//轉換爲紅黑樹
110                         break;
111                     }
112                     if (e.hash == hash &&
113                         ((k = e.key) == key || (key != null && key.equals(k))))//如果鏈表中有該key值,則跳出
114                         break;
115                     p = e;
116                 }
117             }
118             if (e != null) { // existing mapping for key
119                 V oldValue = e.value;//把e的值賦值給 oldValue
120                 if (!onlyIfAbsent || oldValue == null)//如果onlyIfAbsent false 表示可以覆蓋
121                     e.value = value;
122                 afterNodeAccess(e);
123                 return oldValue;
124             }
125         }
126         ++modCount;//操作數加1 ,相當於加了一個版本號
127         if (++size > threshold) //當size大於容器的擴容閾值
128             resize();//開始擴容
129         afterNodeInsertion(evict);//給LinkHashMap 使用的,在HashMap中是一個空方法
130         return null;
131     }
132 
133     /**
134      *HashMap的擴容操作,需要進行
135      * @return the table
136      */
137     final Node<K,V>[] resize() {
138         Node<K,V>[] oldTab = table;//把當前的table 容器備份給oldTab
139         int oldCap = (oldTab == null) ? 0 : oldTab.length;//計算容器的hash桶的數量
140         int oldThr = threshold;//把當前的容器擴容閾值記錄
141         int newCap, newThr = 0;//初始化要創建的新生的容器的hash桶的數量與擴容閾值
142         if (oldCap > 0) { //表示當前容器不是初生的容器
143             if (oldCap >= MAXIMUM_CAPACITY) {//如果當前的hash桶的數量大於最大容量
144                 threshold = Integer.MAX_VALUE;//容器擴容閾值=最大的值
145                 return oldTab;
146             }
147             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
148                      oldCap >= DEFAULT_INITIAL_CAPACITY) //如果當前的容器大小擴大1倍小於最大容量 或當前的容器的大小大於或等於初始化大小
149                 newThr = oldThr << 1; // double threshold 新的容器的擴容閾值是當前容器的2倍
150         }
151         //容器初次創建
152         else if (oldThr > 0) //當前的容器擴容閾值>0
153             newCap = oldThr;//容器的擴容閾值=當前的容器擴容閾值
154         else {               // 使用默認的設置
155             newCap = DEFAULT_INITIAL_CAPACITY;
156             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
157         }
158 
159         //====================
160         if (newThr == 0) {//如果以上的操作後新的擴容閾值依然==0
161             float ft = (float)newCap * loadFactor; //計算新的擴容閾值
162             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
163                       (int)ft : Integer.MAX_VALUE); //新的容器大小 小於最大容量值並且 新計算的ft < 最大容量值 ....
164         }
165         //開始幹正事兒了.....
166         //擴容開始...~~~
167         threshold = newThr;//賦值容器擴容閾值
168         @SuppressWarnings({"rawtypes","unchecked"})
169             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
170         table = newTab;//賦值新容器
171         if (oldTab != null) {//檢查舊容器是否爲空
172             for (int j = 0; j < oldCap; ++j) {//遍歷舊的hash桶
173                 Node<K,V> e;
174                 if ((e = oldTab[j]) != null) {//判斷hash桶中有數據 ,並備份
175                     oldTab[j] = null;
176                     if (e.next == null)//判斷是否是普通數據
177                         newTab[e.hash & (newCap - 1)] = e;//放入新的hash桶中
178                     else if (e instanceof TreeNode)//判斷是否是紅黑樹
179                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//樹重新放入hash桶
180                     else { // 剩下的一定是鏈表,用來解決jdk7中出現的擴容拷貝環問題。
181                         Node<K,V> loHead = null, loTail = null;// 低 定義頭尾
182                         Node<K,V> hiHead = null, hiTail = null;// 高 定義頭尾
183                         Node<K,V> next;
184                         /**
185                          * oldCap一定是2的整數次冪, 這裏假設是2^m 要麼是0 要麼是2^m
186                          * newCap是oldCap的兩倍, 則會是2^(m+1)
187                          * 那麼進行與操作的時候是什麼樣的呢?
188                          * 例如:oldCap=8 ,e.hash=5(這個值隨意)
189                          * 換爲二進制爲 :
190                          * oldCap:1000
191                          * e.hash:0101
192                          * 進行&操作會怎麼樣呢? 得到值爲 oldCap 或 0,
193                          * 爲什麼會這樣? 因爲 oldCap只會在最高位上一位爲 1 那結果只能是這兩種情況
194                          * 那爲什麼要用這種方式來把原鏈表中的數據分成兩個鏈表呢?
195                          * 那就分析一下HashMap的Hash 規則 (n - 1) & hash  ,其中 n 就是 newCap 的擴容閾值 也就是會是 2^(m+1)
196                          * 舉個例子:
197                          * 當前的二進制分別爲
198                          * oldCap:       01000
199                          * oldCap-1:     00111
200                          * newCap:       10000
201                          * newCap-1:     01111
202                          * e.hash:       00101
203                          *
204                          * 也就是說在 e.hash 沒有改變的情況下 ,新的newCap-1比 oldCap-1 多了最高一位 1 那進行hash 計算後的情況 就只有兩種 ,
205                          * 1、跟原來的hash相同 (這樣就會原位不動)
206                          * 2、在新的容器中高位上多了一個1,那就是多了oldCap一倍  (這樣就在原位置上乘以2)
207                          *
208                          * 那再分析一下這個結論跟使用 (e.hash & oldCap) == 0 的關係,那就是==0時, e.hash的關鍵一位(newCap-1比 oldCap-1 多了最高一位 1)是 0,而最高一位不是0的一定是1
209                          * 好了,分析完畢。
210                          */
211                         do {
212                             next = e.next;//備份
213                             if ((e.hash & oldCap) == 0) {//判斷如果==0 不移位
214                                 if (loTail == null)//放到低位鏈表
215                                     loHead = e;
216                                 else
217                                     loTail.next = e;
218                                 loTail = e;
219                             }
220                             else {//否則 移位到高位
221                                 if (hiTail == null)//放到高位鏈表
222                                     hiHead = e;
223                                 else
224                                     hiTail.next = e;
225                                 hiTail = e;
226                             }
227                         } while ((e = next) != null);
228                         if (loTail != null) {
229                             loTail.next = null;
230                             newTab[j] = loHead;//原位不動
231                         }
232                         if (hiTail != null) {
233                             hiTail.next = null;
234                             newTab[j + oldCap] = hiHead;//位置加oldCap一倍
235                         }
236                     }
237                 }
238             }
239         }
240         return newTab;
241     }

超詳細的HashMap 源碼解讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章