栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

HashMap常见面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

HashMap常见面试题

1、HashMap的node

HashMap类有一个非常重要的属性Node是HashMap的一个内部类,实现了Entry接口。本质上是一个映射。如下图:

static class Node implements Map.Entry {
        final int hash; //key的hash值
        final K key; //与你put进去的key一样
        V value; //与你put进去的value一样
        Node next;//存放下一个节点
}
2、HashMap里面的返回值为什么不是key.hashCode()的返回值,而是key.hashCode()^(key.hashCode()>>>16)的返回值呢?

这样做的目的是为了减少hash的冲突概率,计算出hash值h只会,对h和h无符号右移16位做异或运算。实质上就是将底的16为与高的16位异或运算,这样参加计算后,能够减少hash冲突。

为什么要设计成高低16位异或呢?

因为 key.hashCode()函数调用的是 key 键值类型自带的哈希函数,返回 int 型散列值。int 值范围为很大,前后加起来大概 40 亿的映射空间。只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。你想,如果 HashMap 数组的初始大小才 16,用之前需要对数组的长度取模运算,得到的余数才能用来访问数组下标。异或运算还比取模快。

结果显示,当 HashMap 数组长度为 512 的时候(2929),也就是用掩码取低 9 位的时候,在没有扰动函数的情况下,发生了 103 次碰撞,接近 30%。而在使用了扰动函数之后只有 92 次碰撞。碰撞减少了将近 10%。看来扰动函数确实还是有功效的。

3、为什么HashMap要用数组+链表+红黑树实现?

使用数组的原因是因为数组获得元素的时间复杂度为o(1),链表是为了当发生hash冲突时,可以继续存放元素,红黑树是为了在极端情况下二叉树的不平衡。当数组长度大于64且链表长度大于8的时候,链表才会转红黑树。链表长度大于8,数据查找效率变慢,还有一个原因是链表长度大于8发生的概率很小。一开始不适用红黑树是因为红黑树左旋,右旋维护成本太高。

根据jdk代码能够发现,链表长度(也是发生hash碰撞的概率)概率如下图: 

4、HashMap的put方法的大致实现流程

5、HashMap中数组大小为什么要是2的幂次方

因为只有当n为2的幂次方时,才满足hash%n=(n-1)&hash。按位&运算要比%取模快很多倍,十几倍。能保证索引值在范围中,不会超过数组长度。就算你给个长度为11,hashmap内部也会强制转换成你跟你给的值最接近,而且满足等于2的n次幂。

6、HashMap如何计算下标的=

 HashMap中数组下标值的计算过程,大致分为如下几步:获取key.hashCode(),然后将hashCode高16位和低16位异或(^)操作,然后与当前数组长度-1结果进行与(&)操作,最终结果就是数组的下标值。
至于由于当前结点(键值对)的加入,导致当前HashMap中容量超过了阈值而扩容2倍,扩容后导致每个结点重新计算下标值(称为新下标值),新下标值只有两种可能:一、key的hash()返回值新加入与(&)操作计算的一位为0,则下标值与原下标值相同。二、key的hash()返回值新加入与(&)操作计算的一位为1,则计算出来的下标值=原下标值+原数组长度。如果key对象为Integer,他的hashCode()直接返回的value。

7、JDK1.8下的HashMap是如何解决线程安全问题的?

在多线程环境下,1.7 会产生死循环、数据丢失、数据覆盖的问题,1.8 中会有数据覆盖的问题,以 1.8 为例,当 A 线程判断 index 位置为空后正好挂起,B 线程开始往 index 位置的写入节点数据,这时 A 线程恢复现场,执行赋值操作,就把 A 线程的数据给覆盖了;还有++size 这个地方也会造成多线程同时扩容等问题。

通过扰动函数加高低位异或。

8、Java 中有 HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以实现线程安全的 Map。

HashTable 是直接在操作方法上加 synchronized 关键字,锁住整个数组,粒度比较大,Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现;ConcurrentHashMap 使用分段锁,降低了锁粒度,让并发度大大提高。

9、那你知道 ConcurrentHashMap 的分段锁的实现原理吗?

ConcurrentHashMap 成员变量使用 volatile 修饰,免除了指令重排序,同时保证内存可见性,另外使用 CAS 操作和 synchronized 结合实现赋值操作,多线程操作只会锁住当前操作索引的节点。

如下图,线程 A 锁住 A 节点所在链表,线程 B 锁住 B 节点所在链表,操作互不干涉。

10、你前面提到链表转红黑树是链表长度达到阈值,这个阈值是多少?

阈值是 8,红黑树转链表阈值为 6

11、为什么是 8,不是 16,32 甚至是 7 ?又为什么红黑树转链表的阈值是 6,不是 8 了呢?

因为经过计算,在 hash 函数设计合理的情况下,发生 hash 碰撞 8 次的几率为百万分之 6,概率说话。。因为 8 够用了,至于为什么转回来是 6,因为如果 hash 碰撞次数在 8 附近徘徊,会一直发生链表和红黑树的转化,为了预防这种情况的发生。

12、有序的map

LinkedHashMap 内部维护了一个单链表,有头尾节点,同时 LinkedHashMap 节点 Entry 内部除了继承 HashMap 的 Node 属性,还有 before 和 after 用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。

13、LinkedHashMap 和 TreeMap

TreeMap 是按照 Key 的自然顺序或者 Comprator 的顺序进行排序,内部是通过红黑树来实现。所以要么 key 所属的类实现 Comparable 接口,或者自定义一个实现了 Comparator 接口的比较器,传给 TreeMap 用户 key 的比较。

14、JDK1.7的HashMap的rehash底层如何实现的? 15、HashMap中的modcount表示什么意思?

部分内容参考了牧云君博客文章

hashmap头插法和尾插法区别_一个跟面试官扯皮半个小时的HashMap_牧云君的博客-CSDN博客

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/936487.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号