JDK源码阅读之HashMap -- hash值计算方式、下标查找及tableSizeFor方法

本文解析了HashMap中的散列计算方式,包括如何通过hashCode减少碰撞,以及如何利用位运算确保散列值分布均匀。

1. HashMap中的hash计算方式(为什么)

减少hash是的碰撞

2. 下表查找(和hash值的关系)

hashcode ^ (hashcode >>> 16),让低16位同时包含了高位和低位的信息,在计算下标时,由于高位和低位的同时参与,减少hash的碰撞。

(n - 1) & hashCode:n - 1 使得低位全为1,高位全为0,位与之后的结果一定在0 ---> n-1范围内


发展过程:

1. put到HashMap中的Object,我们视为它合理的实现了hashCode方法,该方法可以达到良好的散列效果,即碰很少重复

2. 既然hashCode合理,我们就可以考虑用它来散列(分桶)对象

3. 由于需要将对象散列到一定编号范围的捅中(0 -- n),由于n(表大小)是2的整数次方,可以采用n-1和hashcode按位与,得到的结果是在桶范围内,上述我们是hashCode为散列良好的,所以(n -1)&hashCode的计算结果可视为应该分布均匀的。

4. 上述有一个问题,(n -1)&hashCode的计算方式中,假设n=16,那么hashCode其实只有低4位参与了计算,为了进一步降低碰撞的发生,hashCode的计算方式改为了hashcode ^ (hashcode >>> 16),使得低位中同时包含了低位和高位的信息。



时间问题,暂随意记录,待稍后完善...

<think>好的,我现在需要深入分析JDK 17中的HashMap和ConcurrentHashMap源码,特别是哈希冲突解决机制、树化阈和put()方法的工作流程。首先,我得回忆一下HashMap和ConcurrentHashMap的基本结构。 根据引用[5],JDK7的HashMap使用数组加链表的结构,而JDK8引入了红黑树来优化链表过长的问题。树化阈应该是当链表长度超过一定时转换成红黑树,这个应该是8。不过需要确认在JDK17中是否有变化,但根据引用[3],JDK17的HashMap源码描述中提到红黑树结构,可能树化阈保持8不变。 接下来,哈希冲突解决机制。HashMap使用链地址法,每个数组位置是一个链表或红黑树。当发生哈希冲突时,元素会被添加到链表末尾。当链表长度超过树化阈,就会转成红黑树。但需要考虑哈希表的容量是否达到最小树化容量(默认64),否则会先扩容而不是树化。这部分需要查看源码中的treeifyBin方法。 然后是ConcurrentHashMap的差异。根据引用[1]和[5],JDK7的ConcurrentHashMap使用分段锁,而JDK8之后改为CAS+synchronized。所以在JDK17中,ConcurrentHashMap的实现应该和JDK8类似,使用Node数组,每个桶的头节点作为锁,通过synchronized同步,结合CAS操作保证线程安全。 关于put()方法HashMap的put流程大致是:计算key的哈希,找到数组下标,如果该位置为空则直接插入;如果是链表则遍历到末尾插入,如果链表长度超过阈则树化;如果是红黑树则插入树节点。同时,插入后检查容量是否超过负载因子决定的阈,超过则扩容。而ConcurrentHashMap的put方法使用CAS尝试插入,如果遇到冲突则synchronized锁定头节点,再进行插入,其他步骤类似,但需要考虑并发情况下的线程安全。 在IDEA中调试JDK源码可能需要导入JDK的src.zip源码,并设置断点在HashMap和ConcurrentHashMap的put方法相关位置,比如hash方法、putVal方法、treeifyBin方法等,观察变量变化和流程走向。 需要验证树化阈的具体数,在源码查找常量定义,比如TREEIFY_THRESHOLD和UNTREEIFY_THRESHOLD,以及MIN_TREEIFY_CAPACITY。根据引用[3],可能这些常量在JDK17中仍保持8、6和64的。 另外,哈希冲突的解决还涉及哈希函数的设计。HashMaphash方法可能通过高16位异或低16位来减少碰撞,这在JDK8之后是这样处理的,JDK17应该沿用。而ConcurrentHashMap的哈希计算可能有所不同,需要查看具体实现。 总结起来,回答的结构应该先讲HashMap源码分析,包括结构、哈希冲突处理、树化阈和put流程,然后对比ConcurrentHashMap的不同之处,尤其是线程安全的实现,最后给出调试建议。需要引用提供的参考文献来支持不同版本的差异,比如JDK7和JDK8的变化,以及JDK17的可能延续。</think>### JDK 17 HashMap 和 ConcurrentHashMap 源码解析 #### 一、HashMap 哈希冲突解决机制 1. **链地址法** HashMap 使用数组+链表/红黑树结构(JDK8+)。当发生哈希冲突时,元素以链表形式存储在相同桶位置,链表长度超过阈时转为红黑树[^5]。 - 哈希函数:`(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)`,通过扰动函数减少碰撞[^3]。 - 树化条件:链表长度 ≥8(`TREEIFY_THRESHOLD`)且数组长度 ≥64(`MIN_TREEIFY_CAPACITY`),否则优先扩容。 2. **扩容机制** 默认负载因子 0.75,扩容时链表节点会重新计算位置(`(e.hash & oldCap) == 0` 决定留在原位置或移动到原位置+oldCap)[^3]。 --- #### 二、HashMap put() 方法流程 1. **计算哈希** ```java int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); ``` 2. **定位桶位置** `i = (n - 1) & hash`(n为数组长度)。 3. **处理冲突** - **空桶**:直接插入新节点。 - **链表**:遍历链表,若存在相同key则覆盖;否则尾部插入。长度≥8时触发树化。 - **红黑树**:调用树节点的插入方法。 4. **扩容检查** 若元素总数超过阈(容量×负载因子),进行2倍扩容。 ```java // JDK17 HashMap.putVal() 核心逻辑(简化) final V putVal(int hash, K key, V value, boolean onlyIfAbsent) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { // 处理链表/红黑树插入 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); } if (++size > threshold) resize(); } ``` --- #### 三、ConcurrentHashMap 线程安全实现 1. **JDK8+ 改进** - **分段锁 → 桶锁**:取消分段,改用 `synchronized` 锁单个桶的头节点,结合 CAS 保证原子性[^1]。 - **并发控制**:`sizeCtl` 变量控制初始化与扩容状态,多线程协助扩容。 2. **put() 方法流程** - **CAS 尝试插入**:若桶为空,通过 CAS 写入新节点。 - **锁竞争处理**:若桶非空,使用 `synchronized` 锁住头节点,执行链表/红黑树插入。 - **扩容协助**:插入时检测到正在扩容,当前线程协助迁移数据。 --- #### 四、IDEA 调试 JDK 17 源码 1. **导入源码** 在 IDEA 中关联 `src.zip`(位于 JDK 安装目录)。 2. **关键断点** - `HashMap.putVal()` - `HashMap.treeifyBin()` - `ConcurrentHashMap.putVal()` - `ConcurrentHashMap.transfer()`(扩容逻辑) 3. **观察变量** - `table` 数组结构变化。 - `size` 和 `threshold` 的触发条件。 - 树化阈 `TREEIFY_THRESHOLD` 和最小树化容量 `MIN_TREEIFY_CAPACITY`。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值