HashMap原理(二) 扩容机制及存取原理(2)

HashMap通过resize()方法进行扩容,容量规则为2的幂次

/** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * * @return the table */ final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; //以前的容量大于0,也就是hashMap中已经有元素了,或者new对象的时候设置了初始容量 if (oldCap > 0) { //如果以前的容量大于限制的最大容量1<<30,则设置临界值为int的最大值2^31-1 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } /** * 如果以前容量的2倍小于限制的最大容量,同时大于或等于默认的容量16,则设置临界值为以前临界值的2 * 倍,因为threshold = loadFactor*capacity,capacity扩大了2倍,loadFactor不变, * threshold自然也扩大2倍。 */ else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } /** * 在HashMap构造器Hash(int initialCapacity, float loadFactor)中有一句代码,this.threshold * = tableSizeFor(initialCapacity), 表示在调用构造器时,默认是将初始容量暂时赋值给了 * threshold临界值,因此此处相当于将上一次的初始容量赋值给了新的容量。什么情况下会执行到这句?当调用 * 了HashMap(int initialCapacity)构造器,还没有添加元素时 */ else if (oldThr > 0) newCap = oldThr; /** * 调用了默认构造器,初始容量没有设置,因此使用默认容量DEFAULT_INITIAL_CAPACITY(16),临界值 * 就是16*0.75 */ else { newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //对临界值做判断,确保其不为0,因为在上面第二种情况(oldThr > 0),并没有计算newThr if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) /**构造新表,初始化表中数据*/ Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //将刚创建的新表赋值给table table = newTab; if (oldTab != null) { //遍历将原来table中的数据放到扩容后的新表中来 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; //没有链表Node节点,直接放到新的table中下标为【e.hash & (newCap - 1)】位置即可 if (e.next == null) newTab[e.hash & (newCap - 1)] = e; //如果是treeNode节点,则树上的节点放到newTab中 else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //如果e��面还有链表节点,则遍历e所在的链表, else { // 保证顺序 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { //记录下一个节点 next = e.next; /** * newTab的容量是以前旧表容量的两倍,因为数组table下标并不是根据循环逐步递增 * 的,而是通过(table.length-1)& hash计算得到,因此扩容后,存放的位置就 * 可能发生变化,那么到底发生怎样的变化呢,就是由下面的算法得到. * * 通过e.hash & oldCap来判断节点位置通过再次hash算法后,是否会发生改变,如 * 果为0表示不会发生改变,如果为1表示会发生改变。到底怎么理解呢,举个例子: * e.hash = 13 二进制:0000 1101 * oldCap = 32 二进制:0001 0000 * &运算: 0 二进制:0000 0000 * 结论:元素位置在扩容后不会发生改变 */ if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } /** * e.hash = 18 二进制:0001 0010 * oldCap = 32 二进制:0001 0000 * &运算: 32 二进制:0001 0000 * 结论:元素位置在扩容后会发生改变,那么如何改变呢? * newCap = 64 二进制:0010 0000 * 通过(newCap-1)&hash * 即0001 1111 & 0001 0010 得0001 0010,32+2 = 34 */ else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; /** * 若(e.hash & oldCap) == 0,下标不变,将原表某个下标的元素放到扩容表同样 * 下标的位置上 */ newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; /** * 若(e.hash & oldCap) != 0,将原表某个下标的元素放到扩容表中 * [下标+增加的扩容量]的位置上 */ newTab[j + oldCap] = hiHead; } } } } } return newTab; } 二. get方法

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/eaad98078ef903679c1adca0ee0b285c.html