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/608a8e651e08531350583f151 // 内部节点数组 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方法源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-27本地多文件上传的简单教程
- 2024-11-27低代码开发:初学者的简单教程
- 2024-11-27如何轻松掌握拖动排序功能
- 2024-11-27JWT入门教程:从零开始理解与实现
- 2024-11-27安能物流 All in TiDB 背后的故事与成果
- 2024-11-27低代码开发入门教程:轻松上手指南
- 2024-11-27如何轻松入门低代码应用开发
- 2024-11-27ESLint开发入门教程:从零开始使用ESLint
- 2024-11-27Npm 发布和配置入门指南
- 2024-11-27低代码应用课程:新手入门指南