文章目录
-
- HashMap 底层源码解读(源码分析+知识问答)
- 源码图解
-
- 什么是哈希碰撞?或者什么是哈希冲突?为什么会发生哈希冲突?
- 如何设计哈希函数?你了解哈希函数怎么设计吗?
- 如何避免哈希冲突?
- 为什么HashMap 的负载因子 loadFactor大小为0.75?
- 哈希冲突如何解决?
- 谈一下HashMap的特性?
- 谈一下HashMap的底层原理是什么?
- 我们调用一个 new HashMap<>(),无参的构造方法创建map对象,会发生什么?
- 为什么HashMap 中数组的初始化容量必须是 2的n次幂呢?
- hash & (length-1) 前提是length是2的n次幂 这种算法是如何减少hash碰撞的?
- 我们给hashMap 的构造方法里面传入了一个 数组容量的参数,不是2的幂次方,数组容量是多少,或者会发生什么?
- 我们在添加一个元素的时候,如何找到具体的数组索引的?
- hash函数中怎么计算hash值?为什么要进行高低位运算?为什么不能直接拿 key的hashCode值进行取余的位运算?
- 请说一下 hashmap 的 put方法?
- jdk1.8 hashMap 什么时候会发生扩容呢? 如何扩容呢?
- hashmap 里面的resize方法讲一下
HashMap 底层源码解读(源码分析+知识问答)
源码图解
什么是哈希碰撞?或者什么是哈希冲突?为什么会发生哈希冲突?
不同的关键字通过相同的哈希函数算出了一个相同的 哈希地址,这就叫做哈希冲突。
哈希冲突主要因为 哈希表底层的数组容量是小于实际存储的关键字的数量,所以发生冲突是必然的,我们只能够尽量避免,不能完全消除。
如何设计哈希函数?你了解哈希函数怎么设计吗?
引起哈希冲突的一个方面就是哈希函数设计的不够合理。哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突
设计哈希函数我们的原则是
1.降低碰撞和溢出的产生
2.哈希函数设计简单
3.函数计算的地址尽量均与分布在整个空间提高空间利用率。
常见的哈希函数
常见的哈希函数,可以说出来两三个,随便解释一下
除留余数法
设置散列表中允许的地址数为m,取一个不大于m,但是接近于m或者等于m 的质数p作为除数,按照哈希函数:
Hash(key) = key%p ,将关键字转换成哈希地址
直接定址法
计算关键字的某个线性函数得到哈希地址
Hash(key) = A*key+B
优点: 设计简单、均匀分布
缺点:需要事先知道关键字的分布情况
适用的场景:查找比较小且连续的情况
如何避免哈希冲突?
1.设计更合理的哈希函数
哈希函数设计的越精妙,产生冲突的概率就越低。
2.调节负载因子
什么是负载因子,负载因子就是 填入表中的元素数量/散列表的长度
负载因子和冲突率的关系是成正比的,因为填入表中的元素不能够改变,所以我们只能调节数组的长度。
所以降低冲突率,就是降低负载因子的大小,因此我们只能够把增大散列表的长度(到达阈值扩容)来降低冲突率。
为什么HashMap 的负载因子 loadFactor大小为0.75?
作用很简单,相当于是一个扩容机制的阈值。当超过了这个阈值,就会触发扩容机制。HashMap源码已经为我们默认指定了负载因子是0.75。
当前的容器容量是16,负载因子是0.75;16*0.75=12,也就是说,当容量达到了12的时就会执行扩容操作。
负载因子为1.0的情况
当负载因子是1.0时,也就意味着,只有当数组所有值全部填充了,才会发生扩容。这就带来了很大的问题,因为Hash冲突时避免不了的。
后果:当负载因子是1.0的时候,意味着会出现大量的Hash的冲突。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。
因此一句话总结就是负载因子过大,虽然空间利用率上去了,但是时间效率降低了。
负载因子是0.5的情况
负载因子是0.5的时候,这也就意味着,当数组中的元素达到了一半就开始扩容,既然填充的元素少了,Hash冲突也会减少,那么底层的链表长度或者是红黑树的高度就会降低。查询效率就会增加。
但是,此时空间利用率就会大大的降低,原本存储1M的数据,现在就意味着需要2M的空间。
总之,就是负载因子太小,虽然时间效率提升了,但是空间利用率降低了。
负载因子是0.75的情况
负载因子是0.75的时,空间利用率比较高,而且避免了相当多的Hash冲突,提高了时间查找效率,所以 负载因子是 0.75 体现了时间和空间的权衡。