持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
相信很多同学都在实际开发中经常使用到HashMap,这个是个key-value的容器。在很多应用场景都有大量使用,比如从数据库中获取到大量数据,然后在后续代码中又需要重复使用到这批数据,就可以以数据主键为key,对象为value组装成HashMap,方便后续使用。 知其然而要知其所以然,所以接下来我们就一起来看看HashMap源码都有哪些魔法吧~
1. HashMap中的属性
//默认容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
//负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//转树临界值
static final int TREEIFY_THRESHOLD = 8;
//解树临界值
static final int UNTREEIFY_THRESHOLD = 6;
//转树数组边界值
static final int MIN_TREEIFY_CAPACITY = 64;
//扩容临界值
int threshold;
//负载因子
final float loadFactor;
threshold
扩容的临界值,threshold=initialCapacity*loadFactor,所以在设置HashMap的initialCapacity时,应该根据具体需要装入HashMap的数据量来反推最佳initialCapacity,避免HashMap的扩容操作。
loadFactor
默认值为0.75,这是综合时间和空间开销的最优值,如果大于这个值虽然会减小空间开销,但是会增长操作时间,可能影响的操作包括get和put。
2、扩容方法
扩容的核心方法是HashMap中的resize方法
- 获取HashMap中的数组长度
- 如果数组内有值: 判断数组长度是否大于最大容量(MAXIMUM_CAPACITY)值,如果大于则直接将threshold赋值为Integer.MAX_VALUE,直接返回方法;如果数组长度左移一位(二进制表达向左移一位,空的填0)小于MAXIMUM_CAPACITY且数组长度不小于初始容量,则将原始的容量无符号左移一位; 如果数组内没值: 判断是否初始化了threshold,如果没初始化,则将初始容量设置为默认容量大小(DEFAULT_INITIAL_CAPACITY),将threshold设置为DEFAULT_LOAD_FACTOR*DEFAULT_INITIAL_CAPACITY
- 如果新的threshold未初始化,根据新设置的容量,生成新的threshold
- 根据新的容量值创建数组,如果HashMap中原始的数组存在,则开始进行数据结构的转变。遍历原始数组,获取节点Node,如果当前位置存储对象的next为空,则说明它是单节点,不是树状或者链表结构,直接根据【hash&(length-1)】获取到角标进行设置。如果当前存储对象是treenode,则用split函数处理这颗红黑树。除此之外就是链表结构,当前节点如果hash&oldCap==0,则位置不变,反之角标变成【原索引+oldCap】
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
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;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> 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<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
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;
}