第一篇文章传送门
第二篇文章传送门
本篇开始put方法的分析,先看put的代码:
public V put(K key, V value) {
return this.putVal(hash(key), key, value, false, true);
}
其实put方法调用了putVal方法,所以,put方法的核心还是putVal方法,在看putVal方法之前,putVal中有个hash方法,先来看下hash方法。
static final int hash(Object key) {
int h;
return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
}
在之前提到过扰动函数,这个hash就是扰动函数。
扰动函数作用:让key的hash值的高16位也参与路由运算,增加散列性。
putVal分析
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
HashMap.Node[] tab;
int n;
if ((tab = this.table) == null || (n = tab.length) == 0) {
n = (tab = this.resize()).length;
}
Object p;
int i;
if ((p = tab[i = n - 1 & hash]) == null) {
tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
} else {
Object e;
Object k;
if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
e = p;
} else if (p instanceof HashMap.TreeNode) {
e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
} else {
int binCount = 0;
while(true) {
if ((e = ((HashMap.Node)p).next) == null) {
((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
if (binCount >= 7) {//树化
this.treeifyBin(tab, hash);
}
break;
}
if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
break;
}
p = e;
++binCount;
}
}
if (e != null) {
V oldValue = ((HashMap.Node)e).value;
if (!onlyIfAbsent || oldValue == null) {
((HashMap.Node)e).value = value;
}
this.afterNodeAccess((HashMap.Node)e);
return oldValue;
}
}
++this.modCount;
if (++this.size > this.threshold) {
this.resize();
}
this.afterNodeInsertion(evict);
return null;
}
tab:引用当前hashMap的散列表;
n:表示散列表的长度;
p:当前散列表的元素;
i:表示路由寻址结果的下标;
e:一个临时元素,不为null的话,就找到了一个与当前要插入的key-val一致的key的元素;
k:临时的一个key;
if ((tab = this.table) == null || (n = tab.length) == 0) {
n = (tab = this.resize()).length;
}
创建散列表,将table赋值给tab,如果散列表为空,或者长度为0,那么就初始化散列表。
这样做可以防止HashMap创建完毕后却不使用而造成的浪费。这就是延迟初始化逻辑。
第一次调用putVal方法时会初始化hashMap对象中的最耗内存的散列表。
if ((p = tab[i = n - 1 & hash]) == null) {
tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
}
n-1& hash就是路由算法。
这个if语句就是寻址找到的位置刚好是null,这个时候就将当前的k-v封装为node扔进去。
if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
e = p;
} else if (p instanceof HashMap.TreeNode) {
e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
}
表示位置中的元素,与当前插入元素的key完全一致,表示后续需要进行替换操作
int binCount = 0;
while(true) {
if ((e = ((HashMap.Node)p).next) == null) {
((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
if (binCount >= 7) {//树化
this.treeifyBin(tab, hash);
}
break;
}
//条件成立的话,说明找到了相同的key的node元素,需要进行替换操作
if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
break;
}
p = e;
++binCount;
}
p.next向下一位寻找,如果为空,那么就表示迭代到最后一个元素了,也没有找到一个与要插入key一致的node,说明需要加入到当前链表的末尾。
if (e != null) {
V oldValue = ((HashMap.Node)e).value;
if (!onlyIfAbsent || oldValue == null) {
((HashMap.Node)e).value = value;
}
this.afterNodeAccess((HashMap.Node)e);
return oldValue;
}
当e不等于null,说明找打哦了一个与你插入元素key完全一致的数据,需要进行替换。
++this.modCount;
if (++this.size > this.threshold) {
this.resize();
}
this.afterNodeInsertion(evict);
return null;
modCount表示散列表结构被修改次数,替换Node元素的value不计较。
size则是散列表中node的数量,threshold表示扩容阈值。
如果大于,那么就触发扩容。