【Tips】每日一“面”专栏从今天开始更新~
- 每日一面的素材来自于牛客的面经分享,多为中大厂的面经,实习、校招均有涉及,且经过个人挑选与整理的较为经典的问题。
- 回答则来自于个人总结与AI工具,但不是存粹的AI生成就贴上来了,是经过整理的。
- 另外这里的每天不是完整了一套面试题(因为每天一套面经整理的话,强度太高了…我还有其他事要忙hhh)
- 这一个专栏应该会持续更新到明年的一月…
Q:jdk1.8之前hashmap插入时为啥使用头插法,还让我解释hashmap因为头插法出现死循环是怎么出现的?
具体来说,在发生哈希冲突时,即多个键值对映射到了同一个桶(bucket)上,这些键值对会以链表的形式链接起来。当采用头插法时,新来的元素直接被放置在链表的头部,这样做的好处是:
- 插入效率高:每次插入都是在链表头部进行,因此不需要遍历链表。
- 代码实现简单:头插法实现逻辑相对简单,易于理解和维护
头插法出现死循环:
- 并发修改:当两个或多个线程几乎同时尝试向HashMap中添加元素,并且这些元素正好哈希到了同一个桶(bucket)上时,就可能发生问题。
- 链表结构改变:在头插法下,每次新插入的节点都会被放在链表头部。如果两个线程都试图在相同的桶内插入新的元素,那么它们会各自创建一个新的头节点,并尝试更新桶中的引用指向新的头节点。
- 竞争条件:假设线程A和线程B都在没有正确同步的情况下执行了以下步骤:
- 线程A读取了旧的头节点。
- 线程B也读取了旧的头节点。
- 线程A创建了一个新节点并将其设置为头节点。
- 线程B创建了自己的新节点,但是由于它看到的是已经被线程A更改过的头节点,它将它的新节点链接到由线程A创建的新节点之后。
- 如果此时线程A再次访问这个桶,并且它的新节点意外地指向了线程B的新节点,就会形成一个闭环(环形链表)。
- 死循环:一旦形成了这样的环形链表,后续遍历该链表的操作将会陷入无限循环,因为遍历器无法到达链表的末尾。
为了防止这种问题的发生,在Java 8中,对于HashMap的实现进行了改进,不仅改变了插入方式从头插法改为尾插法,而且引入了红黑树来优化长链表的性能。此外,还提供了线程安全的替代方案如ConcurrentHashMap,它通过分段锁等机制来支持高效的并发访问。如果你的应用需要处理并发情况,建议使用ConcurrentHashMap而不是普通的HashMap。
Q:spring里面是怎么知道这个bean是单例的
使用
ApplicationContext
: 如果你有访问到ApplicationContext
的引用,可以通过isSingleton
方法来判断一个Bean是否是单例。ApplicationContext context = ...; // 获取到你的ApplicationContext boolean isSingleton = context.isSingleton("beanName");
或者
ApplicationContext context = ...; // 获取到你的ApplicationContext BeanDefinition beanDefinition = ((ConfigurableApplicationContext) context).getBeanFactory().getBeanDefinition("beanName"); String scope = beanDefinition.getScope(); boolean isSingleton = "singleton".equals(scope);
Q:jvm中怎么消除STW
STW,即 Stop The World ,指触发Full GC时除了垃圾回收相关线程,其他线程一律停滞
使用低停顿时间的垃圾回收器:
- G1垃圾回收器:适用于大内存应用,具有可调节的停顿时间,可以在后台工作,尽量减少应用程序的停顿时间。
- ZGC(Z Garbage Collector) 和 Shenandoah:这两个垃圾回收器设计目标是实现低停顿时间,适合大规模内存应用。
优化堆内存设置:
- 根据应用的需求合理调整堆的初始大小和最大大小,避免频繁的GC。
- 使用合适的年轻代和老年代比例,以减少GC的次数和时间。
进行并发垃圾回收:
- 使用并发的垃圾回收策略,例如G1中的并发标记阶段,允许应用线程在大多数GC阶段并行执行,减少整体停顿时间。
减少对象创建:
- 通过对象池等设计模式复用对象,减少GC的压力,降低STW事件的频率。
- 优化代码逻辑,避免创建大量短命对象。
调整GC参数:
- 通过JVM启动参数调整GC的行为,例如设置
-XX:MaxGCPauseMillis
以限制GC的最大停顿时间。- 使用
-XX:+UseStringDeduplication
来减少字符串的重复存储,从而降低内存使用。监控和调优:
- 使用JVM提供的监控工具(如VisualVM、JConsole、GC日志)监控GC行为,分析GC的时间和频率,依据数据进行优化。
Q:你了解哪些确保数据库与缓存一致性的算法
这个缓存一致性场景其实很常见,比如类似的操作系统里Cache 和 主存的缓存一致性。
- 首先是最常见的**旁路缓存(Cache Aside)**策略:先访问缓存,如果没有再去访问数据库;写时,先更新数据库再删除缓存;
- 穿透写(Write Through),每次写操作都会同时写入缓存和数据库。
- 回写缓存(Write Back),首先写入缓存,缓存负责异步将数据更新回数据库。
- 延迟双删。更新数据库时,先删除缓存中的数据,然后更新数据库,等待一定时间后再次删除缓存。为了防止写操作期间有其他线程更新了缓存(在写入数据库后,重新读取旧数据),因此需要延时删
Q:MySQL的索引有哪些数据类型
- BTREE 索引:默认且最常用的索引类型,支持范围和精确查询。
- HASH 索引:用于精确查询,但不支持范围查询。
- FULLTEXT 索引:用于全文搜索,适合文本字段。
- SPATIAL 索引:用于地理信息的存储和查询。
- R-Tree 索引:用于多维空间数据的范围查询。