最近看了网上许多分析HashMap的博文,感受颇多,但是很少能看到逐行的注释分析,然后我把自己的理解写一下
数组默认的初始容量 - 必须是2的n次幂。
数组表
HashMap的存储大小
阈(yù)值。HashMap大小达到临界值,需要重新分配大小。
散列表的加载因子。
HashMap大小负载因子,默认为75%
HashMap被修改或者删除的次数总数。
HashMap存储对象的实际实体,由Key,value,hash,next组成。
链表阈值。链表长度大于8转换为红黑树进行处理
数组最大容量
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
//判断是否为初始化的情况
if ((tab = table) == null || (n = tab.length) == 0)
//进行初始化操作
n = (tab = resize()).length;
//判断该数组节点是否为空,精髓之处取模运算: hash%length转换为 (length-1)&hash
if ((p = tab[i = (n - 1) & hash]) == null)
//填充该节点
tab[i] = newNode(hash, key, value, null);
else {
//该数组节点不为空的情况
Node e; K k;
//判断该【数组节点】的hash值和key是否和传入的一样
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//新值替换旧值,先获取引用,后面用来替换值
e = p;
//判断该数组节点是否为红黑树
else if (p instanceof TreeNode)
//将值添加到红黑树
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
//该数组里面有值还不是红黑树,进行循环操作
for (int binCount = 0; ; ++binCount) {
//判断该节点是否为最后一个节点
if ((e = p.next) == null) {
//赋值。这个地方跟Java7不一样,是插在链表尾部!!!
p.next = newNode(hash, key, value, null);
//判断长度是否超过阈值,链表长度超过8,转化成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//红黑树实现
treeifyBin(tab, hash);
break;
}
//【链表中】已存在且hash值和key值都相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//存在相同的hash和key,替换值
if (e != null) { // existing mapping for key
//获取旧值引用
V oldValue = e.value;
//替换值
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//没啥用。。。
afterNodeAccess(e);
return oldValue;
}
}
//记录变化
++modCount;
//判断是否扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node[] resize() {
//获取旧的数组
Node[] 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;
}
//判断旧容量*2倍是否小于最大容量并且大于等于初始容量,
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[] newTab = (Node[])new Node[newCap];
//替换数组
table = newTab;
//判断旧的数组是否为空。如果为空,初始化完成,如果不为空进行扩容
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) {
//处理旧的数据
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;
}
初始化里面就是赋值,其中下面这个方法挺有意思
//为给定的目标容量返回2的幂。例如:输入13返回16,输入8返回16,输入24返回32
static final int tableSizeFor(int cap) {
//防止输入的数就是2的次幂。
int n = cap - 1;
//下面五行主要是将二进制数中低位都转成1,
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//最后加1,低位都变成0!!!!很巧妙
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
https://www.jianshu.com/p/8b372f3a195d
https://juejin.im/post/5a23f82ff265da432003109b
https://tech.meituan.com/java-hashmap.html