Java中HashCode和HashMap详解

发布于 2024-09-13  779 次阅读


数据结构

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 操作涉及三个值:

  1. 内存位置 V:要更新的变量的内存地址。
  2. 期望值 A:线程认为这个位置应该包含的值。
  3. 新值 B:希望将该位置更新为的新值。

CAS 操作的流程

  • 检查内存位置 V 的当前值是否等于期望值 A
    • 如果 V == A,则将内存位置 V 更新为新值 B,操作成功。
    • 如果 V != A,则说明在此期间另一个线程已经更新了该值,CAS 操作失败,线程可以选择重试或者执行其他处理逻辑。

这整个过程是原子的,即要么全部成功,要么全部失败。

ConcurrentHashMap 如何保证线程安全的核心机制

CAS节点插入和更新:简单的值替换直接用CAS。当插入新值时,ConcurrentHashMap 会尝试用 CAS 操作将新节点插入到正确的桶位中。如果 CAS 成功,则插入完成;如果失败,则尝试其他处理方式。

细粒度加锁:如果桶内有值,有只对需要写操作的桶位上锁。用 synchronized

ConcurrentHashMap 为什么 key 和 value 不能为 null?

二义性

Dive into the world.
最后更新于 2024-10-11