HashMap为什么要通过两步 equals和hash来判等
哈希相等的两个对象并不一定完全相等
两个对象相等,两个对象的哈希一定相等
判等步骤
首先判断两个对象的hashCode()是否相等
如果不相等,认为两个对象也不相等
如果相等,则判断两个对象的equals()是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等
put 方法源码及分析
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 声明几个变量
// n table数组长度
// p 当前hash计算出下标,对应的第一个元素
Node<K,V>[] tab; Node<K,V> p; int n, i;
// hashmap 的懒加载,判断如果当前table数组为空,则进行初始化操作
if ((tab = table) == null || (n = tab.length) == 0)
// 初始化
n = (tab = resize()).length;
// 通过hash计算对应的数组的下标,判断当前下标元素是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
// 如果为空,直接newNode对象,放在table[i]第一个位置
tab[i] = newNode(hash, key, value, null);
// 否则说明当前下标还有其他元素
else {
Node<K,V> e; K k;
// 判断新元素,是不是等于当前数组的第一个元素
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 如果就是第一个,直接把p赋值给e
e = p;
// 判断当前节点是不是树类型
else if (p instanceof TreeNode)
// 插入到红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 否则就是链表
else {
// 遍历链表
for (int binCount = 0; ; ++binCount) {
// 判断当前节点的下个节点是否为空,采用尾插法
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果链表元素大于等于8个,就需要考虑树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 判断当前插入节点是否是链表某个节点
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 如果新增的元素的key,已经存在,那么e就是就是对应的那个相同key的对象
// 如果e 为空,那么
if (e != null) { // existing mapping for key
// 记录老的值
V oldValue = e.value;
// 如果找到key相同的节点,判断需不需要进行修改
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 记录操作数
++modCount;
// 判断table中所有的元素是否大于阀值
if (++size > threshold)
// 扩容
resize();
// 空方法
afterNodeInsertion(evict);
return null;
}