当前位置: 首页 > news >正文

每天学一个八股(二)——详解HashMap

文章目录

  • 详解HashMap
    • 说说MashMap的原理
      • HashMap的红黑树优化
      • hashCode()和equals()的重要性
      • 默认容量和负载因子的选择
      • 哈希冲突链表法解决
      • JDK1.8对HashMap除了红黑树还进行了哪些改动?
    • 那么使用HashMap时有哪些性能优化的方法?
      • HashMap扩容机制的性能影响
      • 其他优化

详解HashMap

说说MashMap的原理

HashMap是基于哈希表的数据结构,用于存储键值对key-value)。其核心是将键的哈希值映射到数组索引位置,通过数组+链表(在Java8及之后是数组+链表+红黑树)来处理哈希冲突。
HashMap使用键的hashCode()方法计算哈希值,并通过indexFor方法(JDK1.7及之后版本移除了这个方法,直接使用(n-1)&hash)确定元素在数组中的存储位置。哈希值是经过一定扰动处理的,防止哈希值分布不均匀,从而减少冲突。
HashMap的默认初始容量为16,负载因子为0.75。也就是说,当存储的元素数量超过16x0.75=12个时,HashMap会触发扩容操作,容量x2并重新分配元素位置。这种扩容是比较耗时的操作,频繁扩容会影响性能。

HashMap的红黑树优化

从Java8开始,为了优化当多个元素映射到同一个哈希桶(即发生哈希冲突)时的查找性能,当链表长度超过8时,链表会转变为红黑树。红黑树是一种自平衡二叉搜索树,能够将最坏情况下的查找复杂度从O(n)降低到O(Iogn)。如果树中元素的数量低于6,红黑树会转换回链表,以减少不必要的树操作开销。

hashCode()和equals()的重要性

HashMap的键必须实现hashCode()equals()方法。hashCode()用于计算哈希值,以决定键的存储位置,而equals()用于比较两个键是否相同。在put操作时,如果两个键的hashCode()相同,但equals()返回false,则这两个键会被视为不同的键,存储在同一个桶的不同位置。
**误用hashCode()equals()**会导致HashMap中的元素无法正常查找或插入。

默认容量和负载因子的选择

默认容量是16,负载因子为0.75,这个组合是在性能和空间之间找到的平衡。较高的负载因子(如1.0)会减少空间浪费,但增加了哈希冲突的概率;较低的负载因子则会增加空间开销,但减少哈希冲突。
如果已知HashMap的容量需求,建议提前设定合适的初始容量,以减少扩容带来的性能损耗。

哈希冲突链表法解决

当要塞入一个键值对的时候,会根据一个hash算法计算key的hash值,然后通过数组大小n-1&hash值之后,得到一个数组的下标,然后往那个位置塞入这键值对。

image-20250713143237455

hash算法是可能产生冲突的,且数组的大小是有限的,所以很可能通过不同的key计算得到一样的下标,因此为了解决键值对冲突的问题,采用了链表法,如下图所示:

R-C

在JDK1.7及之前链表的插入采用的是头插法,即每当发生哈希冲突时,新的节点总是插入到链表的头部,老节点依次向后移动,形成新的链表结构。

在多线程环境下,头插法可能导致链表形成环,特别是在并发扩容时(rehashing)。当多个线程同时执行put)操作时,如果线程A正在进行头插,线程B也在同一时刻操作链表,可能导致链表结构出现环路,从而引发死循环,最终导致程序卡死或无限循环。

为了解决这个问题,在JDK1.8的时候,改成了尾插法,即新的节点插入到链表的尾部,保持插入的顺序。并且引入了红黑树。

image-20250713143655348

当链表的长度大于等于8且数组大小大于等于64的时候,就把链表转化成红黑树,当红黑树节点小于6的时候,又会退化成链表。

image-20250713143726750

JDK1.8对HashMap除了红黑树还进行了哪些改动?

  • 改进了哈希函数的计算:JDK1.8中优化了哈希函数,使得哈希值的分布更加均匀,减少了哈希冲突的发生。通过在生成哈希值时使用“扰动函数”,确保哈希值的高低位都能参与到桶的选择中。

1.7是这样的

static int hash(int h) {h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
}

1.8则优化成了这样

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 扩容机制优化:JDK1.8改进了扩容时的元素迁移机制。在扩容过程中不再对每个元素重新计算哈希值,而是根据原数组长度的高位来判断元素是留在原位置,还是迁移到新数组中的新位置。这一改动减少了不必要的计算,提升了扩容效率。

HashMap中的扩容是基于负载因子(load factor)来决定的。默认情况下,HashMap的负载因子为0.75,这意味着当HashMap的已存储元素数量超过当前容量的75%时,就会触发扩容操作。
例如,初始容量为16,负载因子为0.75,则扩容阈值为16×0.75= 12。当存入第13个元素时,HashMap就会触发扩容。
当触发扩容时,HashMap的容量会扩大为当前容量的两倍。例如,容量从16增加到32,从32增加到64等。
扩容时,HashMap需要重新计算所有元素的哈希值,并将它们重新分配到新的哈希桶中,这个过程称为rehashing。每个元素的存储位置会根据新容量的大小重新计算哈希值,并移动到新的数组中。

  • 头插法变为尾插法:头插法的好处就是插入的时候不需要遍历链表,直接替换成头结点,但是缺点是扩容的时候会逆序,而逆序在多线程操作下可能会出现环,产生死循环,于是改为尾插法。

image-20250713144120890

然后1.8是尾插法,每次都从尾部插入的话,扩容后链表的顺序还是和之前一致,所以不可能出现多线程扩容成环的情况。


那么使用HashMap时有哪些性能优化的方法?

  1. 合理设置初始容量:
    如果在使用时可以预估HashMap存储的数据量大小,那么需要在创建时设置一个合适的初始容量,以避免频繁的扩容操作。
    Java 中 HashMap默认初始容量是 16。
  2. 调整负载因子:
    官方提供的默认负载因子是0.75。
    可以根据具体应用场景调整这个值。较低的负载因子会减少冲突,提高查找效率,但会占用更多内存。较高的负载因子则会减少内存消耗,但可能增加冲突的概率,降低查找效率。
  3. 确保 hashCode均匀分布:
    对应key的hashCode0方法生成的哈希值需均匀分布,减少哈希冲突。避免使用质量不高的哈希函数,防止大量键映射到相同的槽位上,造成性能瓶颈。

HashMap扩容机制的性能影响

扩容触发条件:
当HashMap中的元素数量超过容量×负载因子时,会触发扩容。扩容会将容量扩展为当前容量的两倍,并将所有键值对重新分配到新的桶(bucket)中。
性能影响:
扩容是一个耗时的操作,因为它需要重新计算每个键的哈希值,并将键值对重新分配到新的桶中。因此,频繁的扩容会显著影响性能,特别是在存储大量数据时。

其他优化

  • 例如需要保留元素的插入顺序,则可以使用LinkedHashMap替换HashMap。它基于HashMap但维护了一个链表,记录元素的插入顺序。这样就不需要我们从HashMap中获取数据,然后再排序。
  • 如果是需要保留有序的键值对,则可以使用TreeMap。
    kedHashMap替换HashMap。它基于HashMap但维护了一个链表,记录元素的插入顺序。这样就不需要我们从HashMap中获取数据,然后再排序。
  • 如果是需要保留有序的键值对,则可以使用TreeMap。
  • 如果是线程安全场景,则可以使用ConcurrentHashMap。
http://www.dtcms.com/a/278109.html

相关文章:

  • 封装---优化try..catch错误处理方式
  • 【echarts踩坑记录】为什么第二个Y轴最大值不整洁
  • Acrobat 表单中的下拉菜单(附示例下载)
  • 使用docker的常用命令
  • RS4585自动收发电路原理图讲解
  • 从 Manifest V2 升级到 Manifest V3 的注意事项
  • Extended Nested Arrays for Consecutive Virtual Aperture Enhancement
  • 财务管理体系——解读大型企业集团财务管理体系解决方案【附全文阅读】
  • Python异步编程
  • 57.第二阶段x64游戏实战-实时监控抓取lua内容
  • 利用低汇率国家苹果订阅,120 元开通 ChatGPT Plus
  • 14.使用GoogleNet/Inception网络进行Fashion-Mnist分类
  • docker基础部署
  • ID生成策略
  • 在新版本的微信开发者工具中使用npm包
  • 用信号量实现进程互斥,进程同步,进程前驱关系(操作系统os)
  • DOS下EXE文件的分析 <1>
  • MacBook Air通过VMware Fusion Pro安装Win11
  • 从代码学习深度强化学习 - DDPG PyTorch版
  • [Python 基础课程]列表
  • 【DataLoader的使用】
  • 力扣 hot100 Day43
  • Actor-Critic重要性采样原理
  • java valueOf方法
  • 【算法】贪心算法入门
  • SwiftUI 7 新 WebView:金蛇出洞,网页江湖换新天
  • 一些git命令
  • 若依框架集成阿里云OSS实现文件上传优化
  • 对于muduo我自己的理解
  • UniHttp生命周期钩子与公共参数实战:打造智能天气接口客户端