码迷,mamicode.com
首页 > 其他好文 > 详细

HashMap面试

时间:2020-05-31 22:10:53      阅读:101      评论:0      收藏:0      [点我收藏+]

标签:hash冲突   源码   性能   一个   style   位运算   put   hashmap   元素   

1.你知道HashMap底层的数据结构是什么样的吗?

底层最核心的数据结构是数组,我们构造一个map,往里面放入数据,比如我们放入key为张三,value为测试数据,对张三计算出一个hash值,根据这个hash值对数组进行取模,就会定位到数组里的一个元素中去

2.jdk1.8中对hash算法和寻址算法是如何优化的?

还是以map.put("张三","测试数据")为例

对"张三"这个key计算它的hash值,会有一定的优化

hash算法优化

//JDk1.8以后的HashMap里面的一段源码

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

比如说:有一个key的hash值

1111 1111 1111 1111 1111 1010 0111 1100

0000 0000 0000 0000 1111 1111 1111 1111(右移16位:n>>>16)

异或结果:1111 1111 1111 1111 0000 0101 1000 0011

结果是二进制的表示,转换成一个int值,也就是代码里面的h,这个值就是hash值

我们可以看出,其实 (h=key.hashCode())^(h>>>16) 是把hash值的高16位和低16位进行了一个运算

寻址算法优化

(n-1)& hash ->数组里的一个位置  (n代表数组长度,此处n为16)

1111 1111 1111 1111 1111 1010 0111 1100

0000 0000 0000 0000 0000 0000 0000 1111 (n-1:也就是15的二进制)

1111 1111 1111 1111 0000 0101 1000 0011 (优化后的hash值)

取模运算,他是性能比较差一些,为了优化这个数组寻址的过程

hash&(n-1) ->效果是跟hash对n取模,效果是一样的,但是与运算的性能要比hash对n取模要高很多。那为什么要高很多了?这是一个数学问题,数组的长度会一直是2的n次方,只要他保持数组长度是2的n次方,hash对n取模的效果就和hash & (n-1)一样的,而后者的性能更高

如果我们拿没有经过优化的hash值进行与运算,其实高16位之间的运算是可以忽略的,核心点在于低16位的运算,hash值的高16位就没有参与到运算里面来

假设有两个hash值

1111 1111 1111 1111 1111 010 0111 1100

1111 1111 1111 1111 1110 1010 0111 1100

它们两个的低16位是一样的,高16位不同,由于高16位的运算参与可以忽略不计,所以就会造成困扰,那么经过优化后得到的hash值低16位就会有很大不同

总结

hash算法的优化:对每个hash值,在他的低16位中,让高低16位进行了异或,让他的低16为同时保持了高低16位的特征,尽量避免一些hash值后续出现冲突,大家可能会进入数组的同一个位置

寻址算法的优化:用与运算替代取模,提升性能

3.你知道hashMap是如何解决hash碰撞问题的吗?

hash冲突问题,通过链表加红黑树问题来解决

两个key,多个key,算出来的hash值与n-1运算后,定位出来的数组的位置还是一样的,这就是hash碰撞,那怎么解决了?

会在这个位置挂一个链表,这个链表里面放入多个元素,让多个key-value对,同时放在数组的一个位置里

我们get取的时候,如果定位到数组里发现这个位置挂了一个链表,此时遍历链表,从里面找到自己要的那个key-value对

如果只是链表的话,链表很长,那么遍历链表的时候,性能就会比较差,O(n);

所以有了一个优化,当链表的长度达到了一定的长度后,会把链表转换成红黑树,遍历一颗红黑树找一个元素,此时O(logn),性能会比链表高一些

4.说说HashMap是如何进行扩容的可以吗?

底层是一个数组,当这个数组满了以后,他就会自动进行默认二倍扩容,变成一个更大的数组,比如数组16位,就会扩容到32位

数组变大以后会进行一个rehash,重新把数组定位到32位数组里面的位置里面

以一个数组长度为16的Map举例

n-1    0000 0000 0000 0000 0000 0000 0000 1111

hash1 1111 1111 1111 1111 0000 1111 0000 0101

&结果 0000 0000 0000 0000 0000 0000 0000 0101  = 5(index = 5的位置)

 

n-1   0000 0000 0000 0000 0000 0000 0000 1111

hash2   1111 1111 1111 1111 0000 1111 0001 0101

&结果   0000 0000 0000 0000 0000 0000 0000 0101  = 5(index = 5的位置)

在数组长度为16的时候,他们两个hash值的位置是一样的,出现一个hash冲突的问题,用链表来处理

如果数组的长度扩容为32以后,重新对每个hash值进行寻址,也就是每个hash值跟新数组的length-1进行与操作

 

n-1    0000 0000 0000 0000 0000 0000 0001 1111

hash1 1111 1111 1111 1111 0000 1111 0000 0101

&结果 0000 0000 0000 0000 0000 0000 0000 0101  = 5(index = 5的位置)

 

n-1   0000 0000 0000 0000 0000 0000 0001 1111

hash2   1111 1111 1111 1111 0000 1111 0001 0101

&结果   0000 0000 0000 0000 0000 0000 0001 0101  = 21(index = 21的位置)

判断二进制结果中是否多出一个bit的1,如果没多,那么就是原来的index,如果多了出来,那么就是index+oldCap,oldCap就是原来的数组长度,通过这个方式,就避免了rehash的时候,用每个hash对新数组.length取模,取模性能不高,位运算的性能比较高

HashMap面试

标签:hash冲突   源码   性能   一个   style   位运算   put   hashmap   元素   

原文地址:https://www.cnblogs.com/zhangliang1726/p/12170669.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!