HashMap面试题汇总

文章目录

  • HashMap的结构
  • HashMap的扩容机制
    • Hashmap原理总结
  • 为何使用异或操作的原因
  • HashMap的长度为什么是2的n次幂
  • HashMap扩容重新分配元素位置时,为什么低位保持不变,只有高位进行变化
    • 举例子
  • 为什么数组长度达到64且链表长度大于8时,转为红黑树
  • 为什么最开始时不直接用红黑树呢
  • 为什么这个阈值刚好是8呢?
  • 数组长度小于64,链表长度大于8时
  • 总结
  • 【Java主流分布式解决方案多场景设计与实战】

HashMap的结构

  1. 首先我们来看一下HashMap的结构,HashMap的底层是基于数组+链表+红黑树的原理。

  2. 数组特点:查询快,增删慢。

    链表特点:查询慢,增删快。
    红黑树特点:查询快,增删快,是一棵二叉排序树,有自平衡的特点,时间复杂度能控制到O(log(n))。

HashMap的扩容机制

HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树。

Hashmap原理总结

Hashmap是一种常用的数据结构,用于存储键值对,其原理涉及多个关键要点:

  1. 底层存储结构
    它的底层包含一个数组,数组的每个元素可以是链表或者红黑树结构(当链表长度达到一定阈值后会转换为红黑树来优化性能)。

  2. 初始化
    默认情况下,数组容量为16。如果创建Hashmap时未指定初始容量,就会使用这个默认值。

  3. put方法寻址机制
    当向Hashmap中插入元素(put操作)时,需要确定元素在数组中的存储位置,也就是寻址。首先会判断数组(table)是否为空,如果为空,后续会按照默认容量等规则初始化。若不为空,则通过计算元素的键(key)对应的哈希码(hashcode)来确定其在数组中的下标,计算方式是 hashcode & (length - 1),这里的 length 指的是当前数组的长度。

  4. 哈希冲突处理及优化
    为了尽量避免哈希冲突(不同的键经过哈希计算后得到相同的数组下标情况),它采用了一些策略来让哈希值更加随机。会将键的哈希码的高16位和低16位进行异或操作,进一步地,还会将得到的32位哈希码右移16位,然后与自身异或,以此得到一个更加随机的哈希值,再通过前面提到的与运算(hashcode & (length - 1))确定最终的数组下标。这样做使得元素在数组中分布更加均匀,理想情况下查询时间复杂度能达到O(1)。另外,当某个下标对应的链表长度过长(达到一定阈值,默认负载因子是0.75f,即元素个数达到数组容量的0.75倍时可能会考虑调整结构),链表结构就会转换为红黑树结构,以提高查找等操作的效率。

为何使用异或操作的原因

在Hashmap中使用异或操作(高16位和低16位异或以及后续哈希码右移16位后与自身异或)主要有以下几个好处:

  1. 增加随机性
    异或操作能够混合哈希码不同位置的信息,相比于简单的与、或、非操作,它可以更好地打乱原有的数据特征,让最终生成的哈希值更加随机。例如,与操作更多是取交集部分特征,或操作则是合并特征,非操作只是取反,而异或操作在二进制层面上,对于对应位相同则为0(相当于消除了重复特征),不同则为1(保留了差异化特征),使得最终得到的哈希值融合了更多不同位置的信息,减少了相似键产生相同哈希值(即哈希冲突)的可能性。

  2. 均匀分布数据
    通过异或操作后得到的更加随机的哈希值,再结合确定数组下标的计算(hashcode & (length - 1)),能让元素更均匀地分布在数组各个位置上。如果只是采用比较简单的与、或等操作,可能会导致哈希值分布不均匀,某些位置容易堆积较多元素,而其他位置又很空,这样就会增加哈希冲突概率,影响Hashmap整体的查询和插入等操作性能,而异或操作有助于避免这种情况,保障其能接近理想的O(1)查询时间复杂度。

  3. 综合考虑性能与效果
    异或操作在计算机中实现起来相对简单高效,它不需要像一些复杂的哈希函数那样有很高的计算成本,就能在一定程度上达到很好的优化哈希值随机性和均匀分布的效果,符合Hashmap既要保证高效存储和查找,又要尽量减少哈希冲突的设计需求。

所以综合来看,在Hashmap中使用异或操作相较于其他逻辑运算,更有助于优化哈希值以及提升整体的数据存储和查找性能。

HashMap的长度为什么是2的n次幂

  1. 数组下标计算的需要(与运算的高效性)

    • HashMap中,确定元素在数组中的存储位置(下标)的计算方式是hash & (length - 1),其中hash是经过处理后的键的哈希值,length是数组的长度。
    • length为2的n次幂时,length - 1的二进制表示会是n个1。例如,当length = 16(2的4次幂)时,length - 1 = 15,二进制表示为1111
    • 这样的二进制形式使得与运算(&)可以高效地利用哈希值的低位信息来确定下标。因为与运算只有当两个位都为1时结果才为1,所以hash & (length - 1)实际上就是取hash值的最低n位作为数组下标。这种方式计算简单且高效,能够快速地将哈希值映射到数组的一个位置上。
    • 如果length不是2的n次幂,length - 1的二进制表示就不会是连续的1,与运算的结果就不能很好地利用哈希值的低位信息来均匀地分配下标,可能会导致某些下标位置很难被映射到,从而影响元素在数组中的均匀分布。
  2. 减少哈希冲突和优化性能

    • 由于hash & (length - 1)这种计算方式能够有效地利用哈希值的低位信息,并且2的n次幂的长度使得这种计算是一种简单而有效的位运算,这有助于减少哈希冲突。
    • 当元素均匀地分布在数组中时,查询和插入操作的性能可以得到优化。因为在理想情况下,HashMap的查询时间复杂度可以接近O(1)。如果数组长度不是2的n次幂,可能会因为下标计算的不合理导致元素分布不均匀,增加哈希冲突的概率,进而使得链表或者红黑树结构中的元素过多,降低查询和插入的效率。
  3. 扩容机制的便利性

    • HashMap在进行扩容时,新的数组长度通常是原来的2倍(也是2的n次幂)。这种扩容方式使得在重新计算元素在新数组中的位置时,有一定的规律可循。
    • 根据hash & (length - 1)的计算方式,当数组长度翻倍后,元素在新数组中的位置要么是在原来位置(如果哈希值的高位n位为0),要么是在原来位置加上旧数组长度(如果哈希值的高位n位为1)。这种规律使得在扩容过程中重新分配元素位置的操作相对简单高效,减少了数据迁移的复杂性和成本。

HashMap扩容重新分配元素位置时,为什么低位保持不变,只有高位进行变化

  1. 背景知识回顾

    • 首先,在HashMap中,元素在数组中的索引是通过hash & (length - 1)来计算的,其中hash是键的哈希值,length是数组长度(为2的n次幂)。
    • 例如,假设原来数组长度length = 16

你可能感兴趣的:(Java,后端)