HAshMap灵魂考问

422 阅读4分钟

1:HashMap的原理

HashMap使用Entry数组来存储key-value,并且Entry实际上是一个单向链表,有指向下一个节点的指针

2:为什么用数组加链表

数组用来确定元素的存储的桶的位置,如果存在hash值一样的情况就会形成一条链表

3:hash冲突的解决办法

1)开放定址法
2)链地址法
3)再哈希法
4)公共溢出区域法

4:用LinkedList代替数组可以吗

 可以使用LinkedList代替数组

5:为什么HashMap使用数组不使用LinkedList

LinkedList底层是链表结构,链表在查询的效率没有数组好(可以参考ArrayList和LinkedList)

6:为什么不使用ArrayList,ArrayList底层也是数组啊??

 主要是因为扩容,HashMap容量是2的指数次幂,ArrayList扩容是之前的1.5倍,所以不使用ArrayList

7:HashMap在什么条件下扩容

当HashMap实际容量 = 负载因子 * HashMap容量的时候就需要扩容

8:为什么要先高16位异或低16位然后再取模

  为了降低hash冲突的几率,(让高位与低位都参与运算)

9:知道有哪些hash算法

 比较出名的有MurmurHash,MD4,MD5

10:说说String中hashcode的实现(大厂一般问的比较多)

     就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模,因为31是一个奇质数,这种位移与减法结合的计算相比一般的运算块很多

11:知道jdk8中HashMap都改了什么吗

1)由数组+链表改成 数组+链表+红黑树
2)优化了高位运算的hash算法
3)扩容后,元素要么在原位置,要么在原位置+旧数组长度的和的位置,不会出现环链和死锁

12:为什么在解决hash冲突不直接使用红黑树而使用链表

 因为红黑树需要进行左旋和右旋这些操作来保持平衡,而链表不需要

13:为什么不使用二叉树而使用红黑树

 二叉树在极端情况下会形成一条线型结构,遍历查找会很慢

14:当链表转为红黑树后,什么时候转为链表

  链表长度为6的时候转为链表,中间有个差值可以防止链表和树之间频繁转换,如果一个HashMap不停的put和remove,链表个树在8左右之间徘徊,那么就会不停的从链表转为树,树转为链表,这样性能就会很差

15:HashMap在并发环境下有什么问题

     1)多线程扩容,造成死锁和环链(jdk<1.8)
     2)多线程put的时候可能导致元素丢失
     3)put非null元素后get出来的却是null
    所以一般在高并发环境下建议使用ConcurrentHashMap

16:为什么链表的长度大于8就要转红黑树

 理想情况下,桶中节点评率分布遵循 泊松分布,泊松分布是一个概率统计学,在HashMap中,当链表长度等于8的时候,分布在同一个桶中的概率几乎为0,所以才选择8作为这个阀值

17:为什么负载因子默认为0.75

 hashMap的容量 = 默认初始化容量*负载因子,所以当负载因子设置较大,那么可以增加HashMap的容量,但是也增加了链表的长度,就会导致查询效率降低,也就是所谓的 时间换空间 的做法。如果把负载因子设置的较低,那么HashMap的容量就会降低,链表的长度也会降低,查询的效率就会增加,也就是所谓的  空间换时间 的做法。所以取了折中的方法默认为0.75

18:为什么HashMap的容量要是2的幂次方

在HashMap存放元素的时候避免不了键值的hash冲突,我们能解决的就是减少hash冲突,将key值均匀分布在数组上,所以取模就是一个很不错的想法,但是HashMap并没有使用取模运算,而是使用了 & 与运算,因为取模最终还是要转换成二进制运算,那么 & 与运算的效率可以取模效率高。但是使用&运算就不能保证hash的均匀分布,所以当HashMap的容量等于 2的指数次幂的时候会满足一个公式   hash%length = hash & (length -1),所以需要设置成2的指数次幂。第二个原因是 2的指数次幂的值-1,那么永远都是一个单数,所以当hash & (length - 1)的结果一定是在数组长度之内,就不会出现越界。在做扩容的时候判断高低位

19:1.7版本HashMap扩容死锁与环链的形成

    jdk1.7扩容造成环链和死锁需要自己画图理解一下,毕竟这是一个一定要迈过去的坎