最全HashMap面试题,拳拳到肉

129 阅读5分钟

HashMap的底层数据结构是什么

JDK1.7

JDK1.7中,是由数组+链表组成的,数组是HashMap的主体,而链表是为了解决冲突而存在的。

插入链表时采用的是头插法,在并发情况下,会出现成环现象。

JDK1.8

JDK1.8中,是由数组+链表+红黑树存在的

当链表过长时,会影响HashMap的查询性能,红黑树的查找时间复杂度为O(log2N),优于链表的O(n)。

当链表长度 > 8 && 数组长度 >=64的时候会转化为红黑树。

如果数组长度小于64,那么就会先扩容。

当红黑树的个数小于6的时候,红黑树会自动转化成链表

为什么使用红黑树,而不是其他的树结构呢

二叉树

红黑树是不严格平衡二叉树,插入、删除、查找的最坏时间复杂度都是O(log2N),而二叉树会退化成链表时间复杂度为O(n)

二叉平衡树

二叉树是高度平衡的二叉树,查找效率高,但是为了维持这种高度的平衡,每次插入和删除都要做调整,比较复杂耗时。

二叉树的查找优于红黑树,但是插入和删除比不上红黑树

另外,红黑树的性能指标比较稳定,插入、删除、查找都是O(log2N)

B和B+

B+树在数据量不是很多的情况下,数据会全部挤满在一个节点上面,这时候的查找效率就会退化成链表

B树和B+树都很矮胖,比较适合用于磁盘的场景,而红黑树比较适合内存中排序。

红黑树怎么保持平衡的

使用旋转(左旋和右旋)和染色

解决hash冲突的办法有哪些?HashMap用的那种

hashmap使用的是拉链法

  • 开放地址法
  • 再散列法
  • 拉链法
  • 建立公共溢出区

为什么在解决hash冲突的时候,不直接选择红黑树呢

因为红黑树需要左旋、右旋和染色来保持平衡,而单链表不需要这些操作。

  • 一开始使用链表,单链表可以满足查询的性能,而红黑树新增节点比较慢。

  • 当大于8的时候,需要红黑树满足查询性能

HashMap和HashTable的区别

  • 线程安全:HashTable方法的sychonized修饰,线程安全,但是效率比hashmap低
  • 底层数据结构:hashmap当链表长度大于8的时候,会转化成红黑树,但是hashtable一直是链表
  • 初始容量和扩容机制:hashtable默认为11,扩容为2n+1;hashmap默认为16,库容为2的幂次方大小
  • hashmap支持一个null的key和多个null的value,但是hashtable的key和value都不支持null,否则会报空指针异常

JDK1.7和1.8的区别

  • 数据结构
    • 数组+单链表
    • 数组+链表+红黑树
  • 插入方式
    • 头插法,多线程可能成环
    • 尾插法
  • 扩容
    • 7需要全部进行定位
    • 8的话位置不变或者当前位置+旧的size大小
  • 散列函数
    • 7做了四次移位或者四次异或
    • 8只做了一次

扩容因子为什么是0.75呢

扩容因子的话标识的是HashMap的疏密程度。

  • 当扩容因子过大时,虽然空间利用率高,但是哈希冲突增加不利于查找
  • 当扩容因子过小时,虽然哈希冲突减少,但是空间利用率不高
  • 当扩容因子在0.75的时候,遵循参数为0.5的泊松分布。另外在这种情况下,当随机添加值的时候,一个链表的长度大于8,几乎是一个不可能的事件。

hashmap的扩容流程

扩容的条件

当HashMap中的元素超过(数组长度)* 负载因子,或者链表过程时就会进行数组扩容。

新节点的位置

  • HashMap扩容后,节点要不在原来的位置,要不就分配到“原位置+旧容量”的位置。
  • 将所有的元素分为了低位链表(存放下标没变的)、高位链表(存放已经变的)
  • 然后将高低两个链表插入到新数组中

HashMap的key存储索引是怎么计算的

  • 根据Key值计算出hashcode值
  • 根据hashcode计算出hash的值
  • 然后通过hash & (length - 1)计算出存储的位置

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

为什么把key的哈希码右移16位呢

初始化HashMap的时候,如果传入17,它会怎么处理呢?

  • 如果初始化HashMap的时候,如果传入的不是2的n次幂,那么HashMap会向上寻找距离最近的2的n次幂的值,如果是17,那么就初始化位32

为什么HashMap链表转红黑树的阈值为8呢

  • 理想情况下,随机的哈希码,链表里的节点符合泊松分布,节点个数为8的概率几乎为0。而红黑树是保证极端情况下的查找效率。

红黑树回转成链表阈值为什么是6,而不是8

  • 如果回转的阈值也是8,那么可能会导致链表和红黑树不断转换,导致资源的浪费

put方法的流程

image.png

HashMap怎么查找元素

image.png

HashMap为什么线程不安全

  • 多线程扩容死循环
    • 1.7由于采用头插法,在多线程环境下,可能导致环形链表的问题
    • 1.8没有这种问题
  • 多线程put可能导致元素丢失
    • 多线程执行put操作时,如果索引位置相同,那么前面的key可能被后一个key覆盖
  • put和get并发时,可能导致get为null
    • 如果put的时候,元素超出个数突然rehash,此时执行get,就会导致null值