一、前言 最近在研究HashMap
的源码,经过这几天的研究,我对HashMap
的底层实现有了一个比较清晰的认识。今天就来写一篇博客,带大家阅读一下HashMap
中,最最重要的两个方法——get
和put
的代码实现。(注:以下代码基于JDK1.8
)
若想要看懂这两个方法的源代码,首先得对HashMap
的底层结构有一个清晰的认识,若不清楚的,可以看看我之前写的一篇博客,这篇博客对HashMap
的底层结构和实现进行了一个比较清晰和全面的讲解,同时博客的最底下附上了两篇阿里架构师对HashMap
的分析,写的非常好,很有参考价值:
二、解析 2.1 get方法源码解读 get
方法的作用是传入我们需要获取的节点的key
,然后将这个节点的value
返回。首先先贴上get
方法的代码:
1 2 3 4 public V get (Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
可以看到,get
方法的代码非常的简洁,因为具体的代码都封装在了getNode
这个方法里面,get
方法只是对它进行了调用。getNode
方法接收两个参数,第一个参数是key
的hash
值,第二个参数就是key
本身。下面我们就来看看getNode
方法的源代码(通过注释,对源码进行了逐句解读):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 final HashMap.Node<K,V> getNode (int hash, Object key) { HashMap.Node<K,V>[] tab; HashMap.Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1 ) & hash]) != null ) { if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null ) { if (first instanceof HashMap.TreeNode) return ((HashMap.TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null ); } } return null ; }
在HashMap中,containsKey方法也是依赖getNode方法实现的:
1 2 3 public boolean containsKey (Object key) { return getNode(hash(key), key) != null ; }
2.2 put方法源码解读 看完了get
方法的源代码,我们再来看看put
方法。put
方法的作用是将一对key-value
插入到HashMap
中,若HashMap
中已经存在这个key
,则用新的value
替换旧的value
。
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
可以看到,put
方法比get
方法还要简短,和get
方法一样,他也是将实现放入了另一个方法中,这个方法叫做putVal
。我们先来看看这个方法的签名:
1 2 3 4 5 6 7 8 9 10 11 final V putVal (int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
这个方法有5个参数:
hash :需要插入的元素,它的key
的hash
值;
key :需要插入的元素的key
值;
value :需要插入的元素的value
值;
onlyIfAbsent :一个标识,当它的值为false
时,若查询重复的key
值,则将用新的value
替换原来的value
;反之则不替换,留下旧值;
evict :在HashMap
中无意义,将在子类LinkedHashMap
中使用;
除了上面五个参数,在putVal
中还用来另外成员变量,我们要先明确它们的意义:
TREEIFY_THRESHOLD :将链表转换成红黑树的阈值;在HashMap
中,若某一条链表上的节点数大于等于TREEIFY_THRESHOLD
,那就会将它从链表转换成红黑树,这个值默认为8
;
modCount :记录HashMap
被修改的次数,这里的修改仅仅是指插入和删除;这个变量的作用是为了安全的使用迭代器:迭代器在创建时,会记录下这个值,若迭代器在使用的过程中,modCount
与迭代器中记录的值不一致,表示在迭代器被创建后,集合的元素数量发生了改变,这个时候迭代器就不再安全了,此时再使用这个迭代器时将会抛出异常;
size :HashMap
中,节点的数量;
threshold :HashMap
中,当前允许放入的最大节点数,当到达这个数量时,HashMap
将进行扩容;
好了,下面我们就来看看putVal
方法的源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 final V putVal (int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0 ) n = (tab = resize()).length; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { HashMap.Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof HashMap.TreeNode) e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this , tab, hash, key, value); else { for (int binCount = 0 ; ; ++binCount) { if ((e = p.next) == null ) { p.next = newNode(hash, key, value, null ); if (binCount >= TREEIFY_THRESHOLD - 1 ) treeifyBin(tab, hash); break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null ; }
三、总结 为了能够更好的理解,上面的代码我都进行了非常详细的注释,希望对看到这篇博客的人能够有所帮助。因为我对红黑树不是很了解,所以在上面的两个方法中,关于树的操作那部分我都没有深入探讨,之后有时间,我会去专门研究一下红黑树,比较这是在集合中,使用的比较多的一个数据结构。
四、参考 https://blog.csdn.net/qq_35321596/article/details/81117669 https://blog.csdn.net/AJ1101/article/details/79413939
最后更新时间:2020-02-26 01:02:05
世界是个球,前方总有路!