HashMap方法源码分析

2021/4/29 20:25:35

本文主要是介绍HashMap方法源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本文将分析put(),resize(),get()和remove()方法的源码

putval()方法   大致步骤:计算key的hash值;根据hash值计算数组下标;判断下标处是否有节点,无节点则直接插入,有则根据是链表还是红黑树进行对应操作。   这里需要注意的是如果插入链表后长度达到了8,且table数组长度达到64,则会对链表进行树化转为红黑树;如果长度达到了8,且table数组长度小于64,则会进行resize()对table数组扩容,目的是将链表各节点rehash分成高低位两个链表以减少链表长度。   该方法流程图链接https://www.processon.com/view/link/608a8e651e08531350583f15
 1 // 内部节点数组
 2 transient Node<K,V>[] table;
 3 
 4 // 对外界提供的
 5 public V put(K key, V value) {
 6     return putVal(hash(key), key, value, false, true);
 7 }
 8 // 内部实现
 9 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
10                boolean evict) {
11     Node<K,V>[] tab; Node<K,V> p; int n, i;
12     // 如果节点数组未初始化或为空,则进行初始化操作
13     if ((tab = table) == null || (n = tab.length) == 0)
14         n = (tab = resize()).length;
15     // 如果根据hash值得到的数组对应位置还没有元素,则直接插入    
16     if ((p = tab[i = (n - 1) & hash]) == null)
17         tab[i] = newNode(hash, key, value, null);
18     // 如果有元素    
19     else {
20         // hash冲突
21         Node<K,V> e; K k;
22         // 如果hash值和key值相同,则直接替换原值
23         if (p.hash == hash &&
24             ((k = p.key) == key || (key != null && key.equals(k))))
25             e = p;
26         // 如果节点已经是树节点,进行树模式的插入    
27         else if (p instanceof TreeNode)
28             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
29         else {
30             // 如果还是链表,则遍历链表插入数据
31             for (int binCount = 0; ; ++binCount) {
32                 // 这里采用的是尾插法,如果遍历链表过程中没发现key相同节点,则在链表尾部新建节点
33                 if ((e = p.next) == null) {
34                     p.next = newNode(hash, key, value, null);
35                     // 如果链表的长度达到了8,且数组cap大小>=64则转为红黑树
36                     // 如果链表长度达到了8,但数组cap大小<64则resize()扩容
37                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
38                         treeifyBin(tab, hash);
39                     break;
40                 }
41                 // 如果存在key相同的节点,则不插入,退出循环
42                 if (e.hash == hash &&
43                     ((k = e.key) == key || (key != null && key.equals(k))))
44                     break;
45                 p = e;
46             }
47         }
48         if (e != null) { // existing mapping for key
49             V oldValue = e.value;
50             if (!onlyIfAbsent || oldValue == null)
51                 e.value = value;
52             afterNodeAccess(e);
53             return oldValue;
54         }
55     }
56     ++modCount;
57     // 数量超过阈值,进行一次扩容操作
58     if (++size > threshold)
59         resize();
60     afterNodeInsertion(evict);// 回调
61     return null;
62 }

 

resize()方法 大致步骤:   判度当前容量cap和当前阈值thr是否符合扩容条件(翻倍扩容后不超过int最大值)并更新cap和thr的值;   如果可以扩容,则根据新cap新建一个数组;   对原数组进行遍历,复制元素到新数组,有三种情况:无后继节点;链表;红黑树。具体复制细节可以参考作者另一篇博客:https://www.cnblogs.com/ningbing/p/14599571.html
 1 final Node<K,V>[] resize() {
 2     // 获取原有table
 3     Node<K,V>[] oldTab = table;
 4     int oldCap = (oldTab == null) ? 0 : oldTab.length;
 5     int oldThr = threshold;
 6     // 新容量、新阈值
 7     int newCap, newThr = 0;
 8     if (oldCap > 0) {
 9         // 如果原有容量超过设定最大容量,则不进行扩容
10         if (oldCap >= MAXIMUM_CAPACITY) {
11             threshold = Integer.MAX_VALUE;
12             return oldTab;
13         }
14         // 如果原容量翻倍后不超过最大容量且原容量超过16,则进行翻倍扩容
15         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
16                  oldCap >= DEFAULT_INITIAL_CAPACITY)
17             newThr = oldThr << 1; // double threshold
18     }
19     else if (oldThr > 0) // 使用阈值初始化容量,对应的是初始化hashmap带了容量cpacity参数。
20         newCap = oldThr;
21     else {               // zero initial threshold signifies using defaults
22         // 原容量和阈值都<=0,则用默认值初始化,默认容量16,负载因子0.75,阈值12,对应的是hashmap没带参数初始化。
23         newCap = DEFAULT_INITIAL_CAPACITY;
24         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
25     }
26     if (newThr == 0) {
27         // 如果新阈值为0则赋值为新容量*负载因子(默认是0.75)
28         float ft = (float)newCap * loadFactor;
29         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
30                   (int)ft : Integer.MAX_VALUE);
31     }
32     threshold = newThr;// 更新阈值
33     // 基于新容量重新实例化一个node数组
34     @SuppressWarnings({"rawtypes","unchecked"})
35         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
36     table = newTab;// 更新数组
37     if (oldTab != null) {
38         // 遍历数组的每个节点元素
39         for (int j = 0; j < oldCap; ++j) {
40             Node<K,V> e;
41             // 如果节点不为空
42             if ((e = oldTab[j]) != null) {
43                 oldTab[j] = null;// 将原数组节点指向空
44                 // case1:节点只有一个元素,直接根据hash值计算新位置
45                 if (e.next == null)
46                     newTab[e.hash & (newCap - 1)] = e;
47                 // case2:节点为红黑树节点,进行红黑树的复制操作    
48                 else if (e instanceof TreeNode)
49                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
50                 // case3:节点为链表节点,进行链表的赋值操作    
51                 else { // preserve order
52                     // 低位Node链表头节点和尾节点
53                     Node<K,V> loHead = null, loTail = null;
54                     // 高位Node链表头节点和尾节点
55                     Node<K,V> hiHead = null, hiTail = null;
56                     Node<K,V> next;
57                     // 遍历原链表,拆分成低位链表和高位链表
58                     do {
59                         next = e.next;
60                         // 如果是在原位置,则加入低位链表
61                         if ((e.hash & oldCap) == 0) {
62                             if (loTail == null)
63                                 loHead = e;
64                             else
65                                 loTail.next = e;
66                             loTail = e;
67                         }
68                         else {
69                             // 如果不在原位置,加入高位链表
70                             if (hiTail == null)
71                                 hiHead = e;
72                             else
73                                 hiTail.next = e;
74                             hiTail = e;
75                         }
76                     } while ((e = next) != null);
77                     // 如果低位链表不为空
78                     if (loTail != null) {
79                         // 尾部节点赋空并将头部节点放入数组指定位置
80                         loTail.next = null;
81                         newTab[j] = loHead;
82                     }
83                     // 如果高位链表不为空
84                     if (hiTail != null) {
85                         // 尾部节点赋空并将头部节点放入数组指定位置
86                         hiTail.next = null;
87                         newTab[j + oldCap] = hiHead;
88                     }
89                 }
90             }
91         }
92     }
93     return newTab;
94 }
TreeNode split()方法:map扩容时红黑树的拆分+重新存储
 1 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
 2     // 获取自身树节点
 3     TreeNode<K,V> b = this;
 4     // Relink into lo and hi lists, preserving order
 5     // 低位TreeNode链表的头尾节点
 6     TreeNode<K,V> loHead = null, loTail = null;
 7     // 高位TreeNode链表的头尾节点
 8     TreeNode<K,V> hiHead = null, hiTail = null;
 9     // 低位链表节点数量、高位链表节点数量
10     int lc = 0, hc = 0;
11     for (TreeNode<K,V> e = b, next; e != null; e = next) {
12         next = (TreeNode<K,V>)e.next;
13         // 这步操作不是多余的,在e为低位或高位链表最终尾节点时起到赋空作用
14         e.next = null;
15         // 如果仍然在原位置,则加入低位链表
16         if ((e.hash & bit) == 0) {
17             if ((e.prev = loTail) == null)
18                 loHead = e;
19             else
20                 loTail.next = e;
21             loTail = e;
22             ++lc;//低位链表数量+1
23         }
24         else {
25             // 如果是在新的位置(原索引值+oldcap),加入高位链表
26             if ((e.prev = hiTail) == null)
27                 hiHead = e;
28             else
29                 hiTail.next = e;
30             hiTail = e;
31             ++hc;// 高位链表数量+1
32         }
33     }
34     // 低位链表不为空
35     if (loHead != null) {
36         // 低位链表数量不超过6,则深拷贝低位链表并将新链表头部放入数组
37         if (lc <= UNTREEIFY_THRESHOLD)
38             tab[index] = loHead.untreeify(map);
39         else {
40             tab[index] = loHead;
41             // 如果高位链表为空,说明全部元素都在低位链表中,因为原链表已经是树化的了,所以不用再转为红黑树
42             if (hiHead != null) // (else is already treeified)
43                 loHead.treeify(tab);
44         }
45     }
46     // 高位链表不为空
47     if (hiHead != null) {
48         // 高位链表数量不超过6,则深拷贝高位链表并将新链表头部放入数组
49         if (hc <= UNTREEIFY_THRESHOLD)
50             tab[index + bit] = hiHead.untreeify(map);
51         else {
52             tab[index + bit] = hiHead;
53             // 如果低位链表为空,说明全部元素都在高位链表中,因为原链表已经是树化的了,所以不用再转为红黑树
54             if (loHead != null)
55                 hiHead.treeify(tab);
56         }
57     }
58 }

 

get()方法: 大致步骤:   根据key的hashcode计算出hash值(高16位异或低16位填入低16位);   然后将hash值&(cap-1)计算出节点数组下标;   如果没有元素,返回null,有元素则查找链表或查找红黑树判断是否有equals的节点。
 1 public V get(Object key) {
 2     Node<K,V> e;
 3     return (e = getNode(hash(key), key)) == null ? null : e.value;
 4 }
 5 
 6 final Node<K,V> getNode(int hash, Object key) {
 7     Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
 8     // 如果节点数组不为空且数组长度不为0且hash值计算出下标有元素,则继续判断
 9     if ((tab = table) != null && (n = tab.length) > 0 &&
10         (first = tab[(n - 1) & hash]) != null) {
11         // 如果第一个节点就是要找的,直接返回    
12         if (first.hash == hash && // always check first node
13             ((k = first.key) == key || (key != null && key.equals(k))))
14             return first;
15         // 后继有节点    
16         if ((e = first.next) != null) {
17             // 如果是树,则根据hash值和key对红黑树查找
18             if (first instanceof TreeNode)
19                 return ((TreeNode<K,V>)first).getTreeNode(hash, key);
20             // 如果是链表,则遍历各个节点    
21             do {
22                 if (e.hash == hash &&
23                     ((k = e.key) == key || (key != null && key.equals(k))))
24                     return e;
25             } while ((e = e.next) != null);
26         }
27     }
28     return null;
29 }
30 
31 // 树的查找
32 final TreeNode<K,V> getTreeNode(int h, Object k) {
33     // 有根节点从根节点开始找,没根节点从first节点开始找
34     return ((parent != null) ? root() : this).find(h, k, null);
35 }
36 // 红黑树查找
37 final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
38     // 获取调用的节点(根节点或first节点)
39     TreeNode<K,V> p = this;
40     do {
41         int ph, dir; K pk;
42         TreeNode<K,V> pl = p.left, pr = p.right, q;
43         // 当前节点hash值比待找key的hash值大,则进入左子树
44         if ((ph = p.hash) > h)
45             p = pl;
46         // 当前节点hash值比待找key的hash值小,则进入右子树    
47         else if (ph < h)
48             p = pr;
49         // 相同,则比较当前节点是否是待找节点    
50         else if ((pk = p.key) == k || (k != null && k.equals(pk)))
51             return p;
52         else if (pl == null)
53             p = pr;
54         else if (pr == null)
55             p = pl;
56         else if ((kc != null ||
57                   (kc = comparableClassFor(k)) != null) &&
58                  (dir = compareComparables(kc, k, pk)) != 0)
59             p = (dir < 0) ? pl : pr;
60         else if ((q = pr.find(h, k, kc)) != null)
61             return q;
62         else
63             p = pl;
64     } while (p != null);
65     return null;
66 }

 

remove()方法: 大致步骤和get()方法类似:   根据key的hashcode计算出hash值(高16位异或低16位填入低16位);   然后将hash值&(cap-1)计算出节点数组下标;   如果没有元素,返回null,有元素则查找链表或查找红黑树判断是否有equals的节点;   如果找到了节点再根据节点类型和位置执行对应删除操作。
 1 public V remove(Object key) {
 2     Node<K,V> e;
 3     // 调用removeNode方法进行删除
 4     return (e = removeNode(hash(key), key, null, false, true)) == null ?
 5         null : e.value;
 6 }
 7 /**
 8   * @param hash hash for key
 9   * @param key the key
10  * @param value the value to match if matchValue, else ignored
11  * @param matchValue if true only remove if value is equal (如果为true:只有value也相同才移除key相同的节点)
12  * @param movable if false do not move other nodes while removing (如果为false:在删除节点时不能移除其它节点)
13  * @return the node, or null if none
14  */
15 final Node<K,V> removeNode(int hash, Object key, Object value,
16                            boolean matchValue, boolean movable) {
17     // p是要删除节点的父节点(如果是链表结构)                           
18     Node<K,V>[] tab; Node<K,V> p; int n, index;
19     // 如果table数组不为空且hash对应索引位置有元素
20     if ((tab = table) != null && (n = tab.length) > 0 &&
21         (p = tab[index = (n - 1) & hash]) != null) {
22         Node<K,V> node = null, e; K k; V v;
23         // 如果第一个node的hash值和key都与输入参数相同,则为要找的目标
24         // 先比较hash值是为了先用简单条件过滤,equals方法的复杂度要比hash值大多了
25         if (p.hash == hash &&
26             ((k = p.key) == key || (key != null && key.equals(k))))
27             node = p;
28         // 后继有节点    
29         else if ((e = p.next) != null) {
30             // 如果是树化的,通过hash值和key对红黑树进行查找
31             if (p instanceof TreeNode)
32                 node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
33             // 如果是链表,则遍历各个节点    
34             else {
35                 do {
36                     if (e.hash == hash &&
37                         ((k = e.key) == key ||
38                          (key != null && key.equals(k)))) {
39                         node = e;
40                         break;
41                     }
42                     p = e;
43                 } while ((e = e.next) != null);
44             }
45         }
46         // 如果找到了待删除的节点(node不为null,存在)
47         if (node != null && (!matchValue || (v = node.value) == value ||
48                              (value != null && value.equals(v)))) {
49             // 如果是树化的,删除红黑树节点                     
50             if (node instanceof TreeNode)
51                 ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
52             // node=p,说明首节点就是待找节点  
53             else if (node == p)
54                 tab[index] = node.next;
55             // 将待找节点node的父节点p指向node下一个节点。之后node没有任何对象指向它,会被垃圾回收器回收    
56             else
57                 p.next = node.next;
58             ++modCount;
59             --size;
60             afterNodeRemoval(node);
61             return node;
62         }
63     }
64     return null;
65 }

 



这篇关于HashMap方法源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程