用面试题梳理——HashMap底层原理

1.HashMap的特性有哪些?

  1. HashMap是Map接口的一个实现,用来实现键值对的快速存取,key、value都可为null,key值不可重复,重复则后来者覆盖。
  2. 不保证有序。
  3. HashMap的实现不是同步的,这意味着它不是线程安全的。

关键词:存储键值对、存储无序、线程不安全

2.HashMap的底层特性?

  1. HashMap在jdk1.8之前采用 数组+链表 的数据结构,数组在调用构造函数的时候就创建。数组是HashMap的主体,链表则是为了解决哈希冲突(也就是用哈希函数计算出哈希值,然后用哈希值计算出的桶的索引位置相同)。

  2. HashMap在jdk1.8之后采用 数组+链表+红黑树 的数据结构,数组在第一次插入元素时调用扩容函数进行创建。当链表长度大于阈值(默认为8)且数组长度大于64时,将链表转换为红黑树结构,解决链表过长影响查询效率的问题。

关键词:jdk1.8之前的数据结构及各部分作用,jdk1.8之后的数据结构及添加的部分的作用。

3.hashMap中put是如何实现的?

分为三大步,一是计算出元素在数组中的存储索引位置,二是将数据保存到table数组中,三是修改某些成员变量的值并根据情况判断是否扩容。

  1. ①首先要根据键值key计算出哈希值,然后通过位运算实现哈希值的取模,以得到元素在数组中的索引位置。
    ②如果是第一次插入数据(在jdk1.8之后),还要先通过resize()方法对数组进行初始化。
  2. ①判断将要存储的数组位置是否已经存在元素,不存在则说明没有发生哈希碰撞,直接将元素添加到数组中。
    ②存在则说明发生了哈希碰撞,需要依次进行以下判断:
    a.是否是相同的key值,是则覆盖掉旧值替换为新值;
    b.是否是红黑树结构,是则直接插入;
    c.以上判断均不符合,说明是链表结构,则遍历链表在尾部进行插入,在这个过程中如果检查到相同key值元素则直接替换旧值,如果插入元素后链表长度大于阈值则尝试转化为红黑树。
  3. 代表元素个数的size加一,代表修改次数的modCount加一,size超过临界值则进行扩容。

关键词:计算索引位置,保存数据到数组(依次判断:数组索引位置有无元素;key值是否相同;节点类型),扩容。
用面试题梳理——HashMap底层原理_第1张图片
(图来自https://www.cnblogs.com/LiaHon/p/11149644.html)

4.HashMap中什么时候需要进行扩容,扩容resize()又是如何实现的?

(1)扩容时机:

  1. 初始化数组(jdk1.8之后,默认在第一次插入数据的时候才进行数组初始化,初始化是通过调用扩容方法实现的);
  2. 当元素个数超过临界值(临界值=装载因子*数组容量);
  3. 当链表长度超过默认阈值8,尝试转化为红黑树,但发现数组长度不到64时。

(2)扩容机制:

  1. 如果数组未初始化过,会将数组的容量和装载因子都设置为默认值,并将数组创建出来。

  2. 如果数组初始化过,扩容会分配一个新的数组,新的数组长度翻倍,然后遍历整个老结构将元素重新哈希映射到新的数组里。

    HashMap在进行扩容时,使用的重新哈希的方式非常巧妙,因为每次扩容都是翻倍,与原来计算的 (数组长度-1)&hash 的结果相比,只是多了一个bit位,所以结点要么就在原来的位置,要么就被分配到"原位置+旧容量"这个位置。

关键词:三个时机(初始化、临界值、扩容),实现机制(初始化和真正扩容容量翻倍重新分配)

5.谈一下hashMap中get是如何实现的?

主要分为两步,一是根据计算出的哈希值得到数组索引,二是在索引对应的桶上进行查找。

  1. 判断数组非空后,计算出哈希值,得到数组索引。
  2. 如果在桶的首位上就可以找到就直接返回,否则在树中折半查找或者链表中遍历查找。

关键词:计算索引、依次查找

6.谈一下HashMap中hash函数是怎么实现的?还有哪些hash函数的实现方式?

(1)hash函数的实现:对key的hashCode做hash操作,与高16位做异或运算。
(2)其他hash函数实现方式:还有平方取中法,除留余数法,伪随机数法。

7.为什么不直接将key作为哈希值而是与高16位做异或运算?

因为如果直接使用key作为哈希值的话,当产生key的哈希值高位变化大低位变化小的情况时,会发生严重的哈希冲突,可能需要扩容或转化为红黑树结构,影响HashMap的性能。而如果和高16位做异或运算,就能同时用上高十六位和第十六位,增加了随机性,减少哈希冲突的次数。

关键词:哈希冲突、反面(如果不优化的后果)、正面(优化的作用)

8.HashMap的容量为什么必须是2的n次幂?

HashMap为了使数据均匀分布,减少哈希冲突,在计算元素将要存储的数组索引时,会将计算出哈希值再进行取模运算,而为了提升效率,HashMap底层采用 (数组长度-1)&hash 代替 取模运算。

为了使 (数组长度-1)&hash 结果等效于 对hash进行取模运算,所以HashMap的容量必须是2的n次幂,来保证容量减一就是低位全一,可以截取低位进行取模运算。

关键词:取模运算、(数组长度-1)&hash

9.谈一下当两个对象的hashCode相等时会怎么样?

会发生哈希冲突,哈希值一样则计算出的索引位置也会一样,此时后插入的元素就需要插入到尾部,或链表长度到了阈值转化为红黑树或扩容。

关键:哈希冲突

10.如果两个键的hashcode相同,如何获取需要的key对应的值对象?

两个条件:用==比较key的地址相同 或 用equals()比较内容相同。

关键词:比较地址或内容

11.平时在使用HashMap时一般使用什么类型的元素作为Key?

选择Integer,String这种不可变的类型,像对String的一切操作都是新建一个String对象,对新的对象进行拼接分割等,这些类已经很规范的覆写了hashCode()以及equals()方法。作为不可变类天生是线程安全的,而且可以很好的优化比如可以缓存hash值,避免重复计算等等。

关键词:不可变类型,重写hashCode()以及equals()方法,线程安全

12.传统hashMap的缺点(为什么引入红黑树?

JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。

关键词:元素分布不均匀,查找效率低

13.请解释一下HashMap的参数loadFactor,它的作用是什么?

loadFactor表示HashMap的拥挤程度,影响hash操作到同一个数组位置的概率。默认loadFactor等于0.75,当HashMap里面容纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,需要扩容,在HashMap的构造器中可以定制loadFactor。

关键词:拥挤程度,扩容

14.如果让你实现一个自定义的class作为HashMap的key该如何实现?

需要重写他的hashCode方法以及equals方法。

你可能感兴趣的:(java,hashmap)