面经-HashMap

哈希表

应用场景:快速查找

通过计算元素的哈希码,得到元素的桶下标,计算元素位置。接下来只需要进行少量的比较即可找到元素。

 

当链表长度过长时,可以通过扩容减少链表长度。但如果元素的原始哈希码都一样时,即使扩容也无法改变链表长度,只能进行红黑树的树化。

 

底层数据结构,1.7与1.8有何不同?

1.7 数组+链表

1.8 数组+(链表|红黑树)

 

为什么要用红黑树?

红黑树:父节点左边都是比它小的元素;父节点右边都是比它大的元素。

数组链表过长会影响HashMap的性能。引入红黑树后链表过长也不会影响性能。

 

何时会树化?

1.链表长度大于8.

2.数组容量大于等于64.如果不够大会首先尝试扩容。

 

为何一上来不树化?

链表短时性能比红黑树好。树化会占用更多内存,没必要进行树化。

 

树化阈值为何是8?

正常情况下链表不会超过8.遭受到恶意攻击后链表长度才会过长。

1.红黑树用来避免Dos攻击,防止链表过长时性能下降,是偶然现象。hash表时间复杂度为O(1),且Node占用空间小;而红黑树时间复杂度为O(log₂n),且TreeNode占用空间较大。非必要时尽量使用链表。

2.hash值完全随机时,在hash表内按泊松分布。在负载因子为0.75的情况下,长度超过8的链表出现的概率为亿分之6.选择8使树化的几率足够小。

 

何时会退化为链表?

1.扩容时拆分树,树元素<=6时退化链表。

2.remove树节点,若root(根节点)、root.left(左孩子)、root.right(右孩子)、root.left.left(左孙子)有一个为null,也会退化链表。



索引如何计算?

key——>哈希——>二次哈希——>二次哈希%数组长度 / 二次哈希 &(按位与) (数组长度-1) 得到桶下标。

 

HashCode都有了,为何还要提供Hash()方法?

二次哈希可以降低哈希碰撞的概率,使哈希分布更为均匀。

 

数组容量为何是2的n次幂?

计算索引时,2的n次幂是使用按位与【 二次哈希&(数组长度-1) 】运算代替取模运算,效率更高。



put方法流程:

1.hashMap是懒惰创建数组,首次使用才创建数组。

2.计算索引(桶下标)。

3.如果桶下标还没人占用,创建Node占位返回。

4.如果桶下标已经有人占用:

如果已经是TreeNode走红黑树的添加或更新逻辑;

如果是普通Node,走链表的添加或更新逻辑,如果链表长度超过树化阈值,走树化逻辑。

5.返回前检测容量是否超过阈值,一旦超过就进行扩容。

 

 

1.7和1.8的put流程有何不同?

1.8链表插入节点时采用尾插法,新的元素插入到原来链表的尾部。1.7采用头插法。

1.7是大于等于阈值且没有空位时才扩容,而1.8是大于阈值就扩容。

1.8在扩容计算Node索引时,会优化。

 

加载因子为何是0.75f

在空间占用和查询时间之间取得较好的平衡。

大于这个值,空间节省了,但链表就会比较长,影响性能。

小于这个值,冲突减少了,但扩容就会更频繁,空间占用多。

 


多线程下操作HashMap会有什么问题?

线程不安全,会造成数据错乱。先执行的数据可能被后面的数据覆盖掉,造成并发丢失现象。

1.7:扩容并发死链(线程2扩容后,由于头插法,链表顺序颠倒,但是线程1的变量e和next还引用了这两个节点[a引用b,b引用a,造成死链]),数据错乱

1.8:数据错乱

 

key能否为null?作为key的对象有什么要求?

HashMap的key可以为null。但其他Map(HashTable,TreeMap等)都不能为null,否则会出现空指针异常。

作为key的对象,必须重写HashCode和equals方法,并且key的内容不能修改(不可变)。

 

String对象的HashCode是怎样设计的?为何每次乘的都是31?

目的是达到较为均匀的散列效果,使每个字符串的hashCode足够独特。并且31的乘法操作可以优化为效率更高的移位和减法算法。

1.字符串中的每个字符都能表现为一个数字,称为Si,其中i的范围是0~n-1.

2.散列公式为S_0∗31^{(n-1)}+ S_1∗31^{(n-2)}+ … S_i ∗ 31^{(n-1- i)}+ …S_{(n-1)}∗31^0

3.31 代入公式有较好的散列特性,并且 31 * h 可以被优化为

o 即 32 ∗h -h

o 即 2^5 ∗h -h

o 即 h≪5 -h





 
 
 
posted @ 2022-08-23 09:14  临易  阅读(73)  评论(0编辑  收藏  举报