Java – HashMap底层源码分析
hash构造函数分析
// 定义扩容因子为 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
// 在无参构造函数中没什么,只是给了一个扩容因子的值
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
在无构造函数中,创建对象时,HashMap并未进行创建 table 数组。
添加成员分析
public V put(K key, V value) {
// 当执行 put 方法时,会传入5个参数
return putVal(hash(key), key, value, false, true);
}
前4个参数分别是
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
int hash: 利用 key值进行 hash 计算得出哈希特证码
K key: 成员的 key 值
V value: 成员的 value 值
boolean onlyIfAbsent : 当出现相同成员时是否保留(默认为 false)
putVal 方法分析
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
// 定义一个临时节点数组对象,用来存放 table[]
Node<K,V>[] tab;
// 定义一个临时节点对象
Node<K,V> p;
// 用于记录成员数
int n;
// 用于记录table数组索引值
int i;
// 先把 table[] 给tab,如果为空,或tab的长度为0,说明table第一次增加成员
// 需要先创建一个table数组 resize()
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 通过计算数组长度-1 再对将添加的成员 hash进行计算
// 得出成员将被存储到 table 数组中的那个索引位置上
// 并且判断,table 数组的这个索引上是否已有成员
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果没有,直接新建一个节点并存到数组对应索引的位置上
tab[i] = newNode(hash, key, value, null);
else {
// 定义一个临时节点 e ,以备后用
Node<K,V> e;
K k;
// 如果索引上有成员,那么 p 肯定有值,
// 值就是当前存在数组中的成员,就拿这个成员的 hash 和将添加的成员
// 进行 hash 对比,看看是否是同一个 key,
// 如果 hash 一样,继续使用 equals 方法去对比成员的内容属性
// 再一次判断是否完全一样(避免哈希碰撞)
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
// 如果完全一样,会把成员给到临时 e ,此时 e 是旧成员,留作后用(1情况)
e = p;
else if (p instanceof TreeNode)
// 判断 p(旧成员) 是否为红黑树成员,如果是,
// 就把 新成员,添加到红黑树结构中,并给到 e (情况2)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果旧成员和新成员不相同的情况下
// 取出最先的成员,并通过链表的方式,进行一个个的往下对比
for (int binCount = 0; ; ++binCount) {
// 取出旧成员的下一个成员的地址,给到 e
if ((e = p.next) == null) {
// 如果 e 是空的,说明这个成员下面没而节点
// 于是把新成员数据创建一个成员,并挂在旧成员下面
p.next = newNode(hash, key, value, null);
// 判断链表是否超过8,超过8的话,就改为红黑树节点
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
// 如果是通过增加节点,说明没有相同数据,所以直接跳出循环
break;
}
// 对比 e 的 hash 和现在的新成员的 hash 并对比是否完全相等
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
// 如果是,直接跳出循环(情况3)
break;
// 如果链表还没有结束,就会把 e 给到p 让p 在下一个循环中
// 此时e的值是p.next,这里相当于把 p = p.next,给下一次循环对比节点用
p = e;
}
}
// 如果 e 不为空 说明(情况3)存在相同 key 值
if (e != null) {
// 把 旧的成员的 value 值取出来
V oldValue = e.value;
// 判断 是否要保贸,默认不保留 !onlyIfAbsent 为 true
if (!onlyIfAbsent || oldValue == null)
// 把 新成员的 value 值 覆盖 旧成员 的 value 值
e.value = value;
afterNodeAccess(e);
// 并返回 旧的成员 value 值,这就是为什么 put 新成员时,会返回旧成员值的原因
return oldValue;
}
}
++modCount;
// 扩容相关
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
THE END
0
二维码
打赏
海报
Java – HashMap底层源码分析
通过对实现源码进行分析,了解HashMap是如何存储数据的,它的数据结构如何。
TZMing花园 - 软件分享与学习
共有 0 条评论