hashMap相关面试题

132 阅读4分钟
  • hashmap的原理是什么,为什么叫hashMap?

答:HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put(key, value)方法传递键和值时,它先调用key.hashCode()方法,返回的hashCode值,用于找到bucket位置,来储存Entry对象。

  • 如果两个key的hashcode相同,你如何获取值对象?

答:当我们调用get(key)方法,HashMap会使用key的hashcode值,找到bucket位置,然后获取值对象,如果有两个值对象,储存在同一个bucket ,将会遍历链表直到找到值对象,此时并没有值对象,所以找到bucket位置之后,会调用keys.equals()方法,去找到链表中正确的节点,最终找到要找的值对象。

  • 什么是hash碰撞,怎么解决?

答:HashMap使用key的hashcode确定bucket位置,如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,当两个key的hashcode相同时,它们会储存在同一个bucket位置的链表中,并通过键对象key的equals()方法用来找到键值对key-value,HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。

  • HashMap在每个链表节点中,储存的是什么?

答:储存 键值对key-value 对象。

  • HashMap查询时间复杂度是多少,一直是这样吗?为什么查询速度快?

答:Hashmap查找时间复杂度为O(1),这种只是其理想的状态,因为可能存在hash冲突,HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。

  • HashMap是否允许null值,null键,是否是线程安全的?

答:HashMap允许一个null键,多个null值,线程不安全,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。Map map = Collections.synchronizedMap(new HashMap());   或者使用ConcurrentHashMap。

  • HashMap为什么不安全,并发时会导致什么问题?

答:HashMap在接近临界点时,若此时两个或者多个线程进行put操作,可能或造成扩容(resize)和rehash(为key重新计算所在位置),而rehash在并发的时候,在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,可能会形成链表环,在get的时候触发死循环,引起cpu100%问题,但是在1.8后修复了这个问题,扩容时保持了原来链表中的顺序,但是还是不安全。

在put的时候导致的多线程数据不一致
比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的 hash桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的 hash桶索引和线程B要插入的记录计算出来的 hash桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。

  • HashMap在1.8做了哪些优化?

答:在Jdk1.8中HashMap的实现方式做了一些改变,但是基本思想还是没有变得,只是在一些地方做了优化,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式(如下图),在性能上进一步得到提升。如果再问你红黑树,那就是另一个问题了。

  • HashMap是怎么扩容的,如果需要多次向HashMap中put大量元素应该怎么做?

答:HashMap默认容量为16,负载因子为0.75,当达到当前容量的75%时,会进行一次扩容(至两倍),就是创建一个更大的数组,将元素复制过去,HashMap的扩容操作是一项很耗时的任务,所以如果能估算Map的容量,最好给它一个默认初始值,避免进行多次扩容。