HashMap实现原理

HashMap的数据结构: 底层使用hash表数据结构,即数组和链表或红黑树

  1. 当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标

  2. 存储时,如果出现hash值相同的key,此时有两种情况。

a. 如果key相同,则覆盖原始值;

b. 如果key不同(出现冲突),则将当前的key-value放入链表或红黑树中

  1. 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。

一、底层数据结构演进

版本数据结构优化目标
JDK 1.7数组 + 单向链表解决哈希冲突
JDK 1.8数组 + 链表/红黑树防止链表过长导致查询性能退化O(n)→O(log n)

示例场景
当多个键的哈希值映射到同一数组下标(桶)时:

  • 链表:哈希冲突时用链表存储(时间复杂度O(n))
  • 红黑树:链表长度≥8且桶总数≥64时,转为红黑树(时间复杂度O(log n))

二、哈希算法深度优化

1. 哈希扰动函数
 

java

// JDK 1.8的哈希计算:高位参与运算
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

作用

  • 将哈希码的高16位与低16位异或,​增加低位随机性
  • 避免因哈希码分布不均导致大量冲突(如连续数作为键)
2. 下标计算
 

java

// n为数组长度(2的幂)
index = (n - 1) & hash

等效操作hash % n,但位运算效率更高。
为何用2的幂:保证(n-1)的二进制全为1(如16→15=0b1111),使哈希值均匀分布。


三、插入流程(Put)全解析

 

java

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
步骤分解:
  1. 计算哈希值:调用hash(key)扰动
  2. 定位桶位置(n-1) & hash
  3. 处理哈希冲突
    • 空桶:直接插入新节点Node<K,V>
    • 链表:遍历链表,存在相同key则更新value,否则尾插新节点
    • 红黑树:调用TreeNode.putTreeVal()插入
  4. 树化检查:链表长度≥8时,尝试转为红黑树(需数组长度≥64)
  5. 扩容检查:元素总数 > 容量×负载因子(默认0.75)则扩容

链表转红黑树阈值

  • 选择8的统计学依据:哈希冲突符合泊松分布,链表长度≥8的概率极低(约千万分之一)

四、扩容机制(Resize)核心技术

1. 触发条件
  • 元素数量 > 当前容量 × 负载因子(默认16×0.75=12)
2. 扩容过程
  1. 创建新数组:容量翻倍(如16→32)
  2. 数据迁移
    • 链表拆分:根据哈希值高位判断位置(原位置或原位置+旧容量)
       

      java

      // 旧容量为16(二进制0b10000)
      if ((e.hash & oldCap) == 0) {
          // 留在原位置(如index=3)
      } else {
          // 迁移到新位置(如index=3+16=19)
      }
    • 红黑树拆分:节点数≤6时退化为链表
3. 扩容优化
  • 无需重新计算哈希:利用高位快速定位新下标
  • 均摊时间复杂度:O(1)(分摊到每次插入操作)

五、查找流程(Get)原理

 

java

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
步骤分解:
  1. 计算哈希值:同Put流程
  2. 定位桶位置
  3. 遍历结构
    • 链表:顺序查找,调用equals()比较key
    • 红黑树:树查找,时间复杂度O(log n)

关键点

  • 重写equals()必须重写hashCode():确保相同对象返回相同哈希值

六、并发安全问题深度分析

1. 典型问题
  • JDK 1.7头插法死循环:多线程扩容导致链表成环
  • JDK 1.8数据覆盖:多线程put可能丢失数据
2. 解决方案
  • 替代方案:使用ConcurrentHashMap(分段锁/CAS+synchronized)
  • 加锁Collections.synchronizedMap(new HashMap<>())

七、设计哲学与性能调优

1. 关键参数选择
参数默认值作用调优建议
初始容量16减少扩容次数预估元素数量/0.75并取2的幂
负载因子0.75平衡空间与时间效率高查询频率可降低(如0.5)
树化阈值8防止链表查询性能退化非必要不修改
2. 最佳实践
  • 键对象设计
    • 使用不可变对象(如String、Integer)作为键
    • 正确实现hashCode()equals()
  • 预分配容量
     

    java

    // 预期存储100个元素,初始容量=100/0.75≈133 → 取256(2^8)
    Map<String, Object> map = new HashMap<>(256);

八、与其他结构的对比

结构线程安全有序性时间复杂度(平均)适用场景
HashMapO(1)高频读写,无需同步
LinkedHashMap插入序O(1)需要保留插入/访问顺序
TreeMap键排序O(log n)需要自然或自定义排序
ConcurrentHashMapO(1)高并发环境

总结

  • 核心思想:空间换时间,哈希算法决定性能下限,数据结构设计决定上限
  • 适用场景:单线程环境下的高效键值存储,如缓存、临时数据存储
  • 避坑指南:避免在多线程环境直接使用,注意键对象的哈希分布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值