hashmap的11连问

news/2025/2/2 0:57:40/

文章目录

    • 标题1、HashMap 了解吗?平时在什么地方使用过它呢?(说明:发现没有,我喜欢问使用场景,希望大家也是能够思考使用场景的,因为掌握了这个,你说话更加有说服力)
    • 标题2、HashMap 底层数据结构说一下?(指导:直接说最新的即可,不需要去对比以前的版本,因为面试官也听烦了,另外在说的时候,为了你语言的严谨,一定要强调下是哪个JDK版本的哈)
    • 标题3、为什么用红黑树呢?用平衡二叉树不可以吗?或者你讲一讲他们各自的优缺点吗?
    • 标题4、为什么选择 8 之后转为红黑树呢?另外链表转为红黑树之后,还会继续转为链表吗?
    • 标题5、简单描述下 put 的流程?可以说一下JDK为了效率更快,在 put 的时候,做了哪些优化不?
    • 标题6、多线程情况下,put 是线程安全的吗?可以简单举个例子,说一下哪里不安全吗?
    • 标题7、如果我想要让 hashmap 变成线程安全的,你觉得可以怎么做?(有时候会扯到 concurrentHashMap,不过咱们这里先不追击这个)
    • 标题8、头插法会导致死循环,那你觉得在以前的版本中,为啥会使用头插法呢?
    • 标题9、那我们再说一说 HashMap 的扩容吧,什么时候会扩容呢?你觉得负载因子为啥选择 0.75 呢?
    • 标题10、频繁扩容会导致效率比较低下,那你觉得在平时,在实际的开发场景中,可以怎么优化来避免频繁扩容呢?
    • 标题11、一个场景题:只存60个键值对,需要设置初始化容量吗?设置的话设置多少初始化容量比较好呢?

标题1、HashMap 了解吗?平时在什么地方使用过它呢?(说明:发现没有,我喜欢问使用场景,希望大家也是能够思考使用场景的,因为掌握了这个,你说话更加有说服力)

了解,比如通过名称指定配置信息,建立对象与对象的映射关系,设置缓存从而可以避免频繁查询数据库和文件系统,提高效率,使用hashmap存储请求路由规则,根据路由规则以及请求参数查找对应的服务器列表。

标题2、HashMap 底层数据结构说一下?(指导:直接说最新的即可,不需要去对比以前的版本,因为面试官也听烦了,另外在说的时候,为了你语言的严谨,一定要强调下是哪个JDK版本的哈)

JDK >= 1.8的时候是数组+链表+红黑树,Hash值产生碰撞时,链表长度>8会由链表转换成红黑树(将链表转换成红黑树前判断,如果当前数组长度小于64,那么选择先数组扩容,而不是转换成红黑树),当红黑树节点<6的时候,会由红黑树转换成链表,就是二者的性能临界点。

HashMap的默认初始化大小为16,之后每次扩充容量变为原来的2倍,并且HashMap总是使用2的幂作为哈希表的大小。

标题3、为什么用红黑树呢?用平衡二叉树不可以吗?或者你讲一讲他们各自的优缺点吗?

HashMap底层使用红黑树的主要原因是为了在桶中存在大量键值对时提高查找效率。当桶中的链表长度较长时,查找效率可能会变得比较低,这时可以将链表转换为红黑树,以便在O(log n)的时间复杂度内查找键值对。

平衡二叉树的初衷是解决普通二叉树在频繁的插入,删除等动态更新的情况下出现的时间复杂度退化的问题。

红黑树与avl树对比:

红黑树是近似平衡的。相比于avl树,检索时效率差不多,都是通过平衡来二分查找,但是对于插入删除等操作提高很多。红黑树不像avl树一样追求绝对平衡,它允许局部很少的不完全平衡,这样对于效率影响不大,但省去了很多没有必要的调平衡操作,avl树调平衡有时候代价很大,所以效率不如红黑树。

红黑树高度只比高度平衡的AVL树的高度(log2 n)仅仅大一倍,性能上却好很多。

AVL树是一种高度平衡的二叉树,所以查找的非常高,但是有利就有弊,为了维持这种高度平衡,要付出更多代价,每次插入删除都要调整,比较复杂耗时。对于有频繁的插入和删除操作的数据集合,使用AVL树的代价就有点高了。红黑树的插入,删除,查找各种操作性能都比较稳定。

标题4、为什么选择 8 之后转为红黑树呢?另外链表转为红黑树之后,还会继续转为链表吗?

防止链表过长,从而导致查询效率过低,红黑树有自平衡的特点,可以防止不平衡的情况发生,时间复杂度由n变为lgn。

更多还是为了防止用户自己实现了不好的哈希算法时导致链表过长,从而导致查询效率低,转化为红黑树更多是一种保底政策。

红黑树的个数被删为6的时候会回退回链表。

标题5、简单描述下 put 的流程?可以说一下JDK为了效率更快,在 put 的时候,做了哪些优化不?

HashMap提供了put方法添加元素,putval方法只是给put方法调用的一个方法,如果数组为空或者长度为0就扩容,不为空的话,如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不同就判断p是否是一个树节点是否是一个树节点,如果是就调用e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);将元素添加进去,如果不是就遍历链表插入(插入链表的尾部),当链表长度大于阈值并且数组长度超过64就会执行链表转红黑树的操作,否则就只是对数组扩容。

jdk8在扩容时计算数组元素下标时做了优化:

jdk1.7:

重新计算每个元素的哈希值

    static int indexFor(int h, int length) {// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";return h & (length-1);}

jdk1.8:

在扩容时没有像JDK1.7那样,而是通过高位运算e.hash & oldCap,来确定元素是否需要移动。

do {next = e.next;// resize前后,桶的位置未变化// 尾插法插入链表if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}// resize后新产生的桶// 尾插法插入链表else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}
} while ((e = next) != null);
//不同移动
key1.hash = 10 0000 1010
oldCap = 16 0001 0000
//需要移动
key2.hash = 10 0001 0001
oldCap = 16 0001 0000

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWq0wW2m-1686556971002)(学习规划.assets/image-20230612113807172.png)]

标题6、多线程情况下,put 是线程安全的吗?可以简单举个例子,说一下哪里不安全吗?

不安全,如果两个线程同时对一个HashMap操作,一个线程在其中添加或删除元素,另一个正在遍历该HashMap,这种情况可能导致一个线程看到不一致的状态。

import java.util.HashMap;public class SynchronizedHashMapExample {public static void main(String[] args) throws InterruptedException {HashMap<String, Integer> map = new HashMap<>();// 添加元素map.put("A", 1);map.put("B", 2);map.put("C", 3);// 创建锁对象Object lock = new Object();// 第一个线程遍历HashMapThread thread1 = new Thread(() -> {synchronized (lock) {for (String key : map.keySet()) {System.out.println(key + ": " + map.get(key));}}});// 第二个线程在遍历过程中删除一个元素Thread thread2 = new Thread(() -> {synchronized (lock) {map.remove("B");}});thread1.start();thread2.start();thread1.join();thread2.join();}
}

标题7、如果我想要让 hashmap 变成线程安全的,你觉得可以怎么做?(有时候会扯到 concurrentHashMap,不过咱们这里先不追击这个)

jdk7死循环例子:

5张图讲明白JDK1.7下的HashMap死循环(原理+实战) - 知乎 (zhihu.com)

关键点:线程2先扩容完毕,此时布局是这样的,但是线程1却还在按照只有两个桶的时候的布局来进行扩容,读取数据的时候却又是按照T2已经扩容完毕的数据来进行。

可以使用同步机制:可以使用Java中的synchronized关键字来保证多个线程对HashMap的同步访问。在使用synchronized关键字时,需要确保所有对HashMap的访问都在同步代码块中完成,以保证线程安全。

标题8、头插法会导致死循环,那你觉得在以前的版本中,为啥会使用头插法呢?

头插法速度最快,找到数组位置就直接找到插入位置,无需再遍历链表,但是可能会造成多线程同时扩容出现死循环。

标题9、那我们再说一说 HashMap 的扩容吧,什么时候会扩容呢?你觉得负载因子为啥选择 0.75 呢?

  • table为null的时候扩容
  • 数组中的元素个数达到阈值扩容
  • 数组中的链表个数达到8以上,并且数组的大小小于64

0.75是为了触发扩容,减少冲突发生的概率,加载因子很大,扩容条件就会苛刻,hash碰撞概率变高,每个链表长度都很长,查询速度变慢,太小又会导致扩容频率变高,,内存消耗变大。

标题10、频繁扩容会导致效率比较低下,那你觉得在平时,在实际的开发场景中,可以怎么优化来避免频繁扩容呢?

  • 根据业务,给hashmap初始大小一个合理的值减少扩容次数。
  • 选择合适的负载因此控制hashmap的扩容频率
  • 避免hashmap中使用复杂对象作为键,这会增加哈希算法的复杂度,如果使用确保其类覆盖hashcode方法和equals方法。

标题11、一个场景题:只存60个键值对,需要设置初始化容量吗?设置的话设置多少初始化容量比较好呢?

需要,否则就需要多次扩容,取2的幂次方,也就是128即可,因为2的幂次高位是1其余位置是0,当进行hash运算时,(n-1)&hash,n-1就是高位是0,低位都是1,这样&完之后,结果各个位置的取值取决于hash,如果不是2的整数次幂必然会有0位,0与任何&肯定为0,会造成更多的哈希冲突。

回答指导:上面这些问题,不应该需要记住!你需要理解,先理解大概=》看一下源码怎么做的,反正回答出上面这些问题,基本稳


http://www.ppmy.cn/news/345976.html

相关文章

22252

随着互联网的普及和数字化的趋势&#xff0c;数据库管理变得越来越重要。一个高效的数据库管理系统可以提高数据的安全性&#xff0c;提升数据处理速度和准确性。数据库管理不仅仅是将数据存储在一个地方&#xff0c;还包括对数据进行备份和恢复、监控和调优等等。一个好的数据…

23.5.02

下面几题没有理清楚思路

SMTP2BOT | 群晖 消息推送到teleg bot

环境&#xff1a;群晖Docker 问题&#xff1a;想要群晖系统通知到手机APP&#xff0c;server酱没有邮件丰富&#xff0c;邮件还要安装邮件客户端&#xff0c;钉钉和企业微信都是webhook转的&#xff0c;内容不够丰富&#xff0c;只能显示短信的内容 解法&#xff1a;用docker…

redis rdb文件备份还原 linux 持续更新

查看redis当前key的数量 127.0.0.1:6379> info keyspace # Keyspace db0:keys2525,expires0,avg_ttl0bgsave备份redis db库 127.0.0.1:6379> config get dir #获取当前redis备份数据目录 1) "dir" 2) "/usr/local/redis/data"3. 清理redis数据 将…

CNVD-2023-12632 泛微e-cology9 sql注入 附poc

目录 漏洞描述影响版本漏洞复现漏洞修复 漏洞描述 泛微 E-Cology9 协同办公系统是一套基于 JSP 及 SQL Server 数据库的 OA 系统&#xff0c;包括知识文档管理、人力资源管理、客户关系管理、项目管理、财务管理、工作流程管理、数据中心等打造协同高效的企业管理环境&#…

小于 n 的最大数

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路5.实现示例5.1 C5.2 Golang 参考文献 1.问题描述 数组 A 中给定可以使用的 1~9 的数&#xff0c;返回由数组 A 中的元素组成的小于 n 的最大数。 示例 1&#xff1a;A{1, 2, 9, 4}&#xff0c;n2533&#xff0c;返回 2499。…

Android中使用正确参数构建StatFs对象

Android 2.3.3 Eclipse Version: 3.7.0 LogCat LogCat 报错信息&#xff1a; 02-14 11:54:12.834: ERROR/(2525): statfs htc failed, errno: 202-14 11:54:12.844: WARN/System.err(2525): java.lang.IllegalArgumentException02-14 11:54:12.853: WAR…

期末复习C语言2020.12.24

1自守数&#xff08;10分&#xff09; 问题描述&#xff1a;若一个整数a满足条件a*a的尾数等于a则称a为自守数&#xff0c;例如 2525625&#xff0c;76765776&#xff0c; 9376*937687909376 都是自守数。编写程序&#xff0c;求n以内所有自守数。 输入&#xff1a;从键盘随…