HashMap常见面试题

谈一下HashMap的特性?

  1. HashMap以键值对的方式存储,允许null键和null值。
  2. 非线程安全
  3. 底层由hash表实现,无序

谈一下HashMap的底层实现? 

jdk1.8之前采用数组+链表实现,jdk1.8及之后采用数组+链表+红黑树,我们通过put或get进行键值对的添加或获取,当我们添加键值对时首先判断数组是否被初始化,如果没有则进行初始化操作默认数组大小为16,数组初始化完毕后,通过key进行哈希运算获取到key对应数组(桶)的下标,获取到下标对应数组的元素后,如果为空直接添加键值对,否则判断是链表还是红黑树,若是红黑树直接添加,若是链表则需要判断链表长度:链表长度大于8且数组元素小于64则进行扩容、链表长度大于8且数组元素大于64则将链表转换为红黑树,以上操作完成后判断数组元素是否超过阈值,是则进行扩容。

链表或是红黑树在结构中主要用来解决哈希冲突的

谈一下HashMap什么时候需要扩容?扩容是怎么实现的? 

  1. 初始化数组时
  2. 数组长度达到阈值时,initialCapacity*loadFactor
  3. 当链条元素大于8且数组元素小于64时,这时不转换成红黑树而是进行扩容

实现:扩容需要分配一个新数组,新数组是原数组的2倍,遍历老数组把数据hash到新数组中

谈一下HashMap中哈希函数是怎么实现的? 

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

static int indexFor(int h, int length) {
    return h & (length-1);
}

首先根据key的hashcode进行右位移运算(>>>)获取hashcode的高16位数,之后再与hashcode本身做异或(^)操作,获取的值降低了冲突的可能性。该函数也被叫做:扰动函数,说白了就是将hashcode的高16位与低16位进行混合,替换掉hashcode的低16位。

获取到散列值后需要和数组长度做取模操作,HashMap中通过做与操作(&)完成,这也是为什么要求数组容量必须是2的幂次了,只有2的幂次取与操作才相当于取模操作。

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

桶位的确定是通过散列值和数组长度做与(&)操作确定的,实际只使用了散列值的低位(由数组长度确定),如果直接使用哈希值其发生冲突的可能性增加,而HashMap中通过与高16位做异或操作将低位与高位进行混合,这样增加了散列值的随机性,降低了hash冲突的可能性。

谈一下为什么桶必须是2的幂次?如果输入值不是2的幂次比如10会怎么样?

 总的来说是为了使数据均匀的分部在数组的各个位置上,从算法上来说桶位的确定是散列值和数组长度做与运算(&)只有桶的个数是2的幂次才能使散列均匀

如果初始化桶容量时,输入的值不是2的幂次,会使用大于输入值的最小2的幂次

谈一下当两个对象的hash冲突时会怎么样?

 通过链表或红黑树解决

谈一下HashMap的大小超过了负载因子(load factor)定义的容量会做什么操作?

元素个数大于阈值后进行扩容操作,新的容量是原容量的2倍

负载因子:指HashMap容器满的程度,默认是0.75,意思是说当元素个数达到总容量的0.75时进行扩容,有利于降低hash冲突

谈一下HashMap为什么要引入红黑树? 

假设有大量的数据发生冲突,如果是链表,遍历链表时的时间复杂的是O(n),失去了快速查询的优势,而红黑树的时间复杂度是O(logn)

你可能感兴趣的:(HashMap,面试题,java)