此文是基于JDK8的扩容方法。
HashMap是我们工作中常用的一个集合,那么它运行的机制和思想我们了解多少,下面我们就从源码层面来剖析一下,先看下面HashMap中的几个字段属性
//默认初始化的容量向右移动四位也就是2的四次方 容量为16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //最大容量为2的30次方 static final int MAXIMUM_CAPACITY = 1 << 30; //扩容比例,当已使用的数量占到总容量的75%的时候就开始扩容,至于为什么一直都有争议 //我觉得load factory=0.75的真正原因,在java7、8等中均有注释(这段注释在public class //HashMap类定义之前,附注的注释即本文所讨论的注释是在HashMap类定义之后),负载因 //子太小了浪费空间并且会发生更多次数的resize,太大了哈希冲突增加会导致性能不好,所以0.75只是 //一个折中的选择也就是0.5与1的折中,也有人说是通过二项式和泊松分布计算得来的 static final float DEFAULT_LOAD_FACTOR = 0.75f; //链表转红黑树的阈值 static final int TREEIFY_THRESHOLD = 8; //红黑树退化为链表的阈值 static final int UNTREEIFY_THRESHOLD = 6; //链表表转红黑树的时候hash表的最小容量,小于它则优先扩容 static final int MIN_TREEIFY_CAPACITY = 64;
它在扩容的时候与JDK7是有区别的,JDK8的HashMap采用的全新的扩容方式高低位的方式来进行扩容的,下面我们看一下它的扩容代码
final Node[] resize() { //将扩容前的hash表赋值给oldTab Node [] oldTab = table; //oldCap为旧hash表的容量 int oldCap = (oldTab == null) ? 0 : oldTab.length; //扩容前的容量 int oldThr = threshold; //newCap 新的容量,newThr新的扩容阈值 int newCap, newThr = 0; //如果oldCap大于0说明不是第一次初始化 if (oldCap > 0) { //如果旧的hash表的容量大于最大容量,就返回最大容量 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } //否则就在旧的容量基础上扩容两倍 newCap和newThr else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } // 初始容量设置为阈值,也就是我们平时new HashMap的时候会给它指定初 // 始容量的时候会走这里 else if (oldThr > 0) newCap = oldThr; else { // 零初始阈值表示使用默认值,也就是使用默认的容量来初始化 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //如果上面的走完之后newThr还是等于0那就给他初始化默认的值 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 [] newTab = (Node [])new Node[newCap]; //把新的赋值给table table = newTab; //如果oldTab不等于空说明里面有值需要进行扩容,否则直接返回一个空得table if (oldTab != null) { //这里就是根据旧的数组进行循环依次迁移处理,这里用到的 for (int j = 0; j < oldCap; ++j) { Node e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode )e).split(this, newTab, j, oldCap); else { // preserve order Node loHead = null, loTail = null; Node hiHead = null, hiTail = null; Node next; do { next = e.next; if ((e.hash & oldCap) == 0) {//因为hashmap的容量必须是2的倍数,所以它只会出现两种情况要么是0要么是oldCap if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { //否则就是高位比如16扩容到32,hash & oldCap的值为16即代表高位 if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
Java8中的HashMap扩容跳过了Jdk7扩容的坑,对源码进行了优化,采用高低位拆分转移方式,避免了链表环的产生。
扩容前:
扩容后:
从Jdk8引入了红黑树的结构,put方法也发生了改变,具体过程如下图:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)