Java 面试题集 -- 001

Java 面试题集

1. 请说一下HashSet的底层实现原理

HashSet的本质上是基于hashmap实现的Set接口集合,它的底层是一个HashMap,我们存放的元素是存到了HashMap的key上,而Value则统一用了一个Present 的 Object对象占位;由于HashMap的key值不允许重复,HashSet就是利用这样特性来保证元素不重复。具体判断逻辑是,先比较HashCode是否相同,如果相同则去使用equals方法比较内容是否相同,如果都相同就认为两者是同一个元素,需要实现去重。

2. 既然你说是基于HashMap,既然你说是基于 HashMap,那如果我要往 HashSet 里存自定义的对象(比如 User 类),需要做什么特殊处理吗?为什么

需要重新对象的hashcode值和equal方法,如果不重写hashset会默认根据对象的内存地址来判断对象是否重复,这样即使两个对象的内容完全一致,但是他们是不同的实列对象,就会被当作不重复元素添加进去,导致去重失败。

而且重写了hashcode必须重写equal方法,hashcode比较只是起到快速筛选的作用,如果不重写hashcode,如果不同,则会直接判定不相等,不调用equals方法

而如果只重写hashcode不重写equals方法,就会导致比较两个对象的内存地址,即使内容相等,但是比较也是false,这是因为equals方法比较的是对象的引用地址,两个相同内容的实例对象的引用地址是不同的,所以必须重写equals方法,

3. 在 MySQL 中,索引(Index)是干什么用的?它有什么优缺点?我们在写 SQL 语句时,有哪些情况会导致索引失效?

Mysql的默认索引是采用了B+树的数据结构来实现的,它通过空间换取时间的方式,极大的降低了I/O操作次数,从而方便快速搜索。

它的优缺点,优点是提高了查询速度,缺点是会占用物理存储空间,并执行新增、修改、删除操作时,需要额外去动态的维护索引结构,降低写操作的性能。

在实际开发中,我总结了以下几种常见的索引失效情况:

  • 违反最左前缀原则:在使用联合索引时,查询条件必须从索引的最左列开始,中间不能断开或跳过。
  • 对索引列进行运算或使用函数 :比如 WHERE age + 1 = 10WHERE DATE(create_time) = '2023-01-01',这会导致索引失效。
  • LIKE 以通配符 % 开头 :比如 WHERE name LIKE '%卷卷',这种左模糊查询无法利用 B+Tree 的有序特性。
  • OR 条件导致失效:如果 OR 连接的其中一个字段没有索引,或者存在隐式类型转换,可能导致整个 SQL 放弃使用索引。
  • 隐式类型转换 :比如索引列是字符串类型,查询时却用了数字(WHERE phone = 138...),数据库会自动转换类型,导致索引失效。
  • 使用不等于 (!= 或 <>):查询优化器认为全表扫描可能比走索引更快,从而放弃索引。"

4. Spring 中的声明式事务(@Transactional)是如何实现的?如果在 Service 类的一个普通方法 A 中调用了另一个加了 @Transactional 注解的方法 B,事务失效了,你觉得可能是什么原因?如何解决?

第一,实现原理:

Spring 的声明式事务是基于 AOP(面向切面编程) 实现的。底层默认使用 JDK 动态代理或 CGLIB 来创建代理对象。当外部调用一个标注了 @Transactional 的方法时,实际上是调用的代理对象。代理对象负责在目标方法执行前开启事务,执行后提交事务,如果捕获到异常则进行回滚。

第二,失效原因:

这种失效通常被称为**"自调用"问题。** 当方法 A 调用同一个类中的方法 B 时,由于是通过 this 关键字直接调用的,调用链路并没有经过 Spring 容器生成的代理对象。既然没有经过代理,代理对象中关于 B 方法的事务拦截器(Interceptor)自然也就不会生效,导致 B 的事务注解被忽略。

第三,解决方案:

除了将事务逻辑合并到 A 方法中(给 A 也加上注解),我更推荐以下两种方式:

  1. 服务拆分:将 B 方法抽取到一个新的 Service 类中,通过 Spring 的依赖注入来调用。这是符合单一职责原则的,也是最推荐的做法。
  2. 自我注入 :在当前类中注入自己(需要加上 @Lazy 防止循环依赖),然后通过注入的实例来调用 B 方法,这样就能强制走代理逻辑。
  3. 架构调整:如果业务逻辑允许,尽量避免这种内部方法的事务嵌套,通过上层服务来协调。"

🚀 进阶追问(准备一下)

面试官可能会继续追问你对事务传播行为的理解:

"如果必须在同一个类中处理,且 A 方法已经有事务,B 方法必须独立提交(比如记日志),即使 A 回滚了 B 也不能回滚,这个时候 @Transactional 的传播行为应该设置成什么?"

  • 考察点Propagation.REQUIRES_NEW
  • 答案 :应该设置为 REQUIRES_NEW。但这依然解决不了"自调用"问题,必须配合上面的"提取 Service"才能生效。

5. 除了刚才说的自调用问题,如果在 Spring 事务中,我们执行了一条 SQL 更新语句,紧接着查询这条数据,发现查不到更新后的值,导致后续业务逻辑出错。除了代码逻辑问题,你觉得数据库层面可能是什么原因导致的?如何解决?

这个问题非常典型,通常是由 MySQL 的 MVCC(多版本并发控制)机制事务隔离级别共同导致的。

1. 原因分析

MySQL 的默认隔离级别是 REPEATABLE READ 。在这个级别下,事务在执行过程中会维护一个一致性视图(Read View)。当我们在这个事务中先执行更新(Update),紧接着执行查询(Select)时:

  • 更新操作:会生成一个新的数据版本。
  • 后续查询 :如果是一个普通的快照读(Snapshot Read,即普通的 SELECT),它读取的依然是事务开启时的那个快照,而不是最新的版本。因此,查询结果可能还是旧的,导致看起来像是'更新失效'或者'查不到数据'。

2. 解决方案

针对这个问题,我通常有以下几种解决思路:

  • 方案一(推荐):使用"当前读"
    在查询 SQL 后面加上 FOR UPDATE 或者 LOCK IN SHARE MODE 。加锁的查询属于当前读(Current Read),它会忽略 MVCC 的快照,直接读取最新的数据版本,这样就能查到刚刚更新的值了。
  • 方案二(代码优化):利用缓存或变量
    既然是在同一个事务、同一个方法流中,刚更新完的数据其实已经在内存里了(比如 MyBatis 的一级缓存/Session 缓存)。我们可以直接从缓存中获取,或者直接使用之前 set 好的 Java 对象,避免无谓的数据库回查,这样性能也更高。
  • 方案三(不推荐):调整隔离级别
    虽然将隔离级别改为 READ COMMITTEDREAD UNCOMMITTED 理论上能解决可见性问题,但这会带来脏读、不可重复读的风险,严重破坏数据一致性,在生产环境中是绝对禁止为了这种业务逻辑去调整的。"
相关推荐
2501_921649492 小时前
如何获取美股实时行情:Python 量化交易指南
开发语言·后端·python·websocket·金融
C182981825752 小时前
HttpURLConnection 与其他客户端关系
java
集智飞行2 小时前
c++函数传参的几种推荐方式
开发语言·c++
通往曙光的路上3 小时前
发邮件1、创建邮箱
java
鼾声鼾语3 小时前
matlab的ros2发布的消息,局域网内其他设备收不到情况吗?但是matlab可以订阅其他局域网的ros2发布的消息(问题总结)
开发语言·人工智能·深度学习·算法·matlab·isaaclab
麦麦鸡腿堡3 小时前
Java_类的加载
java·开发语言
我命由我123453 小时前
VSCode - Prettier 配置格式化的单行长度
开发语言·前端·ide·vscode·前端框架·编辑器·学习方法
JIngJaneIL3 小时前
基于java + vue校园快递物流管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js
超级大只老咪3 小时前
数组的正向存储VS反向存储(Java)
java·开发语言·python