数据结构
Node<K, V>
Node<K,V>[] table; //存kv的数组
key的哈希值:
static final int hash(Object key) {
int h;
return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
}
检查 key
是否为 null
来处理空键,然后使用 key.hashCode()
生成哈希值,并对哈希值进行扰动处理,防止低质量的哈希函数导致哈希表中大量的哈希冲突。
一个类如果没有重写hashCode()会咋样
JVM会处理该对象在内存中的引用地址,通过某种转换或处理形式生成hashCode。
putVal的逻辑
putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)
参数解释:
hash:这是键的哈希值。
key:要插入的键。
value:要插入的值。
onlyIfAbsent:如果为 true,则仅在没有当前键的情况下更新值。
evict:与缓存相关,指示是否逐出元素,通常与LRU等缓存策略结合使用。
1.初始化哈希表,如果为空则进行扩容
2.根据哈希值定位桶的位置.
3.如果冲突,检查链表或树
3.1.检查是否与当前节点键相同
如果哈希值相同,且 key 也相同(或者通过 equals() 方法判断相等),后续直接修改当前节点value
3.2.检查是否为树节点
调用 putTreeVal() 方法将新的键值对插入到树结构中。
3.3.检查是否为链表节点
遍历链表来处理冲突
如果找到相等的键,则退出循环,不需要插入新节点,只需要更新值。
如果到达链表的末尾,还未找到相同的键,就在链表尾部插入新节点。
如果链表长度超过7(即 binCount >= 7),将链表转换为红黑树,以提高效率(即 treeifyBin 操作)。
4. 更新旧值或插入新值
HashMap存储的table长度为啥是2的幂次?
方便取余:取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作
更好地保证哈希值的均匀分布:有一半的元素会挪到新开辟的区域中
方便元素迁移:只需检查哈希值高位的变化来决定元素的新位置(hash高位为0时不变,hash高位为1,原索引位置+原容量)
HashMap线程安全
没锁,大家都可以直接对hashmap进行操作。
插入元素的时候,可能会出现数据覆写问题。
例如:
线程A和B分别要存储俩Value不同但Key相同的元素,此时A和B都发现桶没有元素。
然后A先存储,B可能会覆盖掉。
ConcurrentHashMap如何实现HashMap线程安全?
并发控制使用 synchronized
和 CAS 来操作。
CAS是啥
一种用于实现无锁并发操作的机制。
CAS 操作涉及三个值:
- 内存位置 V:要更新的变量的内存地址。
- 期望值 A:线程认为这个位置应该包含的值。
- 新值 B:希望将该位置更新为的新值。
CAS 操作的流程:
- 检查内存位置
V
的当前值是否等于期望值A
。- 如果
V == A
,则将内存位置V
更新为新值B
,操作成功。 - 如果
V != A
,则说明在此期间另一个线程已经更新了该值,CAS 操作失败,线程可以选择重试或者执行其他处理逻辑。
- 如果
这整个过程是原子的,即要么全部成功,要么全部失败。
ConcurrentHashMap
如何保证线程安全的核心机制
CAS节点插入和更新:简单的值替换直接用CAS。当插入新值时,ConcurrentHashMap
会尝试用 CAS 操作将新节点插入到正确的桶位中。如果 CAS 成功,则插入完成;如果失败,则尝试其他处理方式。
细粒度加锁:如果桶内有值,有只对需要写操作的桶位上锁。用 synchronized
ConcurrentHashMap 为什么 key 和 value 不能为 null?
二义性
Comments NOTHING