一、前言 最近在研究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
世界是个球,前方总有路!