标签:sig 存储 key ++ 如何 app final 重点 长度
1. putVal方法
该方法主要做以下几件事:
(1) 首先判断HashMap底层的table是否初始化,如果没有,就调用resize()方法进行初始化table操作. 注意resize方法即可以初始化table操作,也可以对table进行扩容
(2) 根据当前key的hash值和table的size值,计算key对应的valu值应该存储在table表中的下标值,记为i
(3) 如果table[i]为空,就创建一个Node节点(节点封装了key,value相关的数据)存放在table[i]上
(4) 如果table[i]已经有值了,我们将该值记为p,注意这个p肯定table表中的元素,同时也可能是链表中的头节点, 这又分成3种情况处理
<1> 如果key与p节点的key完全相等,那就覆盖oldValue
<2> 如果p是一颗树.....(跳过,没看懂)
<3> 除去上面两种情况之外,P的屁股后面肯定挂着一个链表, 这就需要对链表中的每个元素进行遍历,判断当链表中的节点key与当前put的key是否相等,如果相等,也是将oldValue进行覆盖,否则就是new一个新的Node节点,然后挂在链表的屁股后面. 同时也会对这个链表的节点长度进行判断,如果超过8,则会调用treeifyBin方法,进行链表转树的操作
(5) 源码
/** * Map put方法的实现 */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 如果table是null,就是还没有初始化(jdk8中, table是在第一次使用的时候初始化的) if ((tab = table) == null || (n = tab.length) == 0) // resize()方法对table进行初始化或者2倍扩容 n = (tab = resize()).length; // table的length减1与当前key的hash值的与运算,即是这个key在table中存储下标 // & 运行, 二进制位数都是1结果才是1,否则是0 // table中是否存储着当前key对应的value值 if ((p = tab[i = (n - 1) & hash]) == null) // 不存在这个key,如果存在,就挂链表 // 创建一个新的Node,存储到table表中下标为i的slot位置 tab[i] = newNode(hash, key, value, null); else {// 挂链表 Node<K,V> e; K k; // 1. 对table中的数据进行覆盖判断,因为p是链表的头节点,是存放在table中的 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 将覆盖前的p赋值给e, 注意:这儿并没有进行覆盖 // 2. 处理树的情况(先跳过) else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 3. 链表的情况,有hash冲突的数据,直接挂在原节点的next上 else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // 将key,value封装成一个Node节点,然后挂在p的next上 p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 链表转树结构 treeifyBin(tab, hash); break; } // 对链表节点中数据进行覆盖判断,注意前面有段相同的代码,那是对table中的数据(链表头节点)进行覆盖操作 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break;// 如果key相同,break跳出for循环,执行后面的逻辑 p = e; } } // 存在映射关系的key if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; // 用新的value值去覆盖老的value值 afterNodeAccess(e); return oldValue; // 返回覆盖前的value值 } } // 记录HashMap修改的次数 ++modCount; // 记录key-value映射的次数,相当于HashMap的size if (++size > threshold) // 如果size大于threshold,就需要进行扩容 resize(); // 移除更老的数据, 这里暂时不看 afterNodeInsertion(evict); return null; // 返回null }
2. resize方法
该方法有两个作用,一是对table进行初始化操作,一是对table进行扩容操作. 原则上每次扩容2倍. 这个方法的重点是看它如何将oldTab中的元素转移到newTab中去的.
源码使用了for循环,遍历出oldTab中的每一个元素,我们记为e, 然后再对e的相关属性进行判断, 同样分为3种情况
(1) 如果e.next==null, 表明e节点屁股后面即没跟树,也没跟链表,即是e.key无hash冲突的情况. 这样情况最简单, 通过计算e.hash值然后& 扩容后的table长度,即为e在newTab中的存放位置
(2) e节点就是一颗树的情况, 跳过
(3) e屁股后面挂着链表的情况,也没看太懂
源码显示,通过 e.hash & oldCap 将e屁股后面挂的链表拆分成了两个链表, 然后将这两个新的链表分析挂在newTab的两个槽位上. 这儿比较神奇,原本处于同一个链表结构的数据(oldTab),有hash冲突, 现在通过扩容,挂在了newTab的两个槽位上,表明这两个槽位的中key不存在hash冲突了, 这是不是从侧面说明了,扩容减少了hash冲突的机率.
源码
// 对HashMap底层table进行初始化或者扩容 final Node<K,V>[] resize() { // 1. 将原先的table赋值给变量oldTab Node<K,V>[] oldTab = table; // oldTab的容量值,即原table中有多少个元素 int oldCap = (oldTab == null) ? 0 : oldTab.length; // 原先扩容的阈值 int oldThr = threshold; // 定义了两个变量,新的table的容量和阈值 int newCap, newThr = 0; if (oldCap > 0) { // 表示原table中有元素 if (oldCap >= MAXIMUM_CAPACITY) { // 如果原来table(扩容前)的元素个数大于等于 1073741824 threshold = Integer.MAX_VALUE; // 直接将阈值设置为Integer的Max_VALUE值 return oldTab; } // newCap在 oldCap的基础扩容1倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; // 这儿就是第一次使用时,对table进行初始化 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // 将初始化的这个newTab赋值到table // 下面应该是重点: 扩容(将扩容前table中的元素移动到扩容后的table中去). 如果是初始化, 不会进入if条件里面去 if (oldTab != null) { // 通过for循环遍历取出扩容前table中的每个元素 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; // 只对非空元素进行处理 if ((e = oldTab[j]) != null) { // 将oldTab中的j号位置的元素取出来赋值给e这个变量 oldTab[j] = null; // 将oldTab中j号位置置空 // 下面这段逻辑就是将扩容前table中的元素移动到扩容后table, 具体分为3种情况 //1 . 第一种情况,也是最简单的, 无hash冲突,也就是说无链表 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; // 将e这个元素放到newTab(扩容后的table)的 e.hash & (newCap - 1) 这个位置 // 有hash冲突,并且后面的链表已经转成了红黑树 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); // 有hash冲突,但后边还是链表 else { Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; // 这个do...while循环将oldTab[j]元素后面链表中的节点分别挂在两个链表上,一个是lo...., 一个是hi...., // 然后将lo,hi两个拆分出来的链表挂在扩容后的newTab的不同位置上 // 感觉还是没看懂... do { next = e.next; if ((e.hash & oldCap) == 0) { // lo.. 链表 if (loTail == null) loHead = e; else loTail.next = e; loTail = e; }else { // hi..链表 if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; // 将lo链表挂到newTab[j]位置上 } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; // 将hi链表挂到newTab[j + oldCap]位置上 } } } } } return newTab; }
未完等续.....
标签:sig 存储 key ++ 如何 app final 重点 长度
原文地址:https://www.cnblogs.com/z-qinfeng/p/12115647.html