第一轮面试 面试官:先问些基础的,Java 中 ArrayList 和 LinkedList 的区别是什么? 王铁牛:ArrayList 是基于数组实现的,随机访问快,LinkedList 是基于链表实现的,插入和删除操作快。 面试官:不错,回答得很清晰。那 HashMap 在 JDK1.7 和 JDK1.8 中有什么主要区别? 王铁牛:1.8 用了红黑树,1.7 是链表,1.8 解决了哈希冲突时链表过长性能问题。 面试官:很好。那 Spring 框架中 IOC 是什么? 王铁牛:控制反转,把对象创建和管理交给 Spring 容器。
第二轮面试 面试官:刚刚说的 IOC 底层原理能详细讲讲吗? 王铁牛:呃……就是通过反射机制创建对象,然后放到容器里。 面试官:那 Spring Boot 相比于 Spring 有什么优势? 王铁牛:Spring Boot 自动配置,能快速搭建项目,不用像 Spring 那样配置很多东西。 面试官:MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译,{} 是字符串替换,#{} 能防止 SQL 注入。
第三轮面试 面试官:Dubbo 服务调用的流程是怎样的? 王铁牛:嗯……就是服务提供者注册服务,消费者从注册中心获取服务,然后调用。 面试官:RabbitMQ 如何保证消息不丢失? 王铁牛:好像是开启确认机制,还有持久化什么的。 面试官:xxl - job 调度任务执行失败了,你会怎么排查问题? 王铁牛:看看日志,检查下任务配置,可能是代码有问题。
面试官:好的,今天的面试就到这里,你回去等通知吧。我们会综合评估你的表现,无论结果如何,都会在一周内给你回复。感谢你今天来参加面试。
问题答案:
- ArrayList 和 LinkedList 的区别:
- 数据结构:ArrayList 基于动态数组,LinkedList 基于双向链表。
- 随机访问:ArrayList 支持随机访问,通过索引直接定位元素,时间复杂度为 O(1);LinkedList 随机访问慢,需从头或尾遍历,时间复杂度为 O(n)。
- 插入和删除:ArrayList 在中间插入或删除元素需移动大量元素,时间复杂度为 O(n);LinkedList 在中间插入或删除只需修改指针,时间复杂度为 O(1)。但如果是在列表末尾操作,ArrayList 直接添加,LinkedList 也需遍历到末尾,此时 ArrayList 性能更好。
- HashMap 在 JDK1.7 和 JDK1.8 的区别:
- 数据结构:JDK1.7 采用数组 + 链表结构,JDK1.8 采用数组 + 链表 + 红黑树结构。当链表长度大于 8 且数组容量大于 64 时,链表会转换为红黑树,以提高查找性能。
- 哈希冲突解决:JDK1.7 采用头插法,在多线程环境下可能形成环形链表导致死循环;JDK1.8 采用尾插法,避免了这个问题。
- 扩容机制:JDK1.7 扩容时,需重新计算哈希值和索引位置,复制元素到新数组;JDK1.8 优化了扩容算法,部分元素在扩容时位置不变,减少了重新计算哈希值的开销。
- Spring 框架中 IOC 底层原理:
- 反射机制:Spring 通过反射获取类的构造函数,创建对象实例。例如,对于一个类
UserService,Spring 可以通过Class.forName("com.example.UserService")获取类对象,然后通过Constructor创建实例。 - 依赖注入:当创建对象时,如果对象有依赖关系,Spring 会通过反射获取对象的属性或方法,将依赖对象注入进去。比如
UserService依赖UserDao,Spring 会创建UserDao实例并注入到UserService中。 - Bean 容器:Spring 使用
BeanFactory或ApplicationContext作为 Bean 容器,管理创建的对象。容器负责对象的生命周期管理,如初始化、销毁等。
- 反射机制:Spring 通过反射获取类的构造函数,创建对象实例。例如,对于一个类
- Spring Boot 相比于 Spring 的优势:
- 自动配置:Spring Boot 基于约定大于配置的原则,能根据项目依赖自动配置 Spring 应用。例如,添加
spring - boot - starter - web依赖,Spring Boot 会自动配置好 Web 开发所需的 Servlet 容器、Spring MVC 等。 - 快速搭建项目:减少了大量繁琐的 XML 配置或 Java 配置代码,开发者可以更专注于业务逻辑开发。
- 内置服务器:Spring Boot 内置了 Tomcat、Jetty 等服务器,可直接打包成可执行的 JAR 或 WAR 文件,方便部署。
- 自动配置:Spring Boot 基于约定大于配置的原则,能根据项目依赖自动配置 Spring 应用。例如,添加
- MyBatis 中 #{} 和 ${} 的区别:
- #{}:预编译方式,将传入的数据当作字符串处理,在 SQL 语句中会用占位符
?代替参数值。例如SELECT * FROM user WHERE username = #{username},执行时会将#{username}替换为?,然后设置参数值,能有效防止 SQL 注入。 - **{username}'`,如果传入的值是恶意 SQL 语句,可能导致 SQL 注入。一般用于传入数据库对象,如表名、列名等,但使用时要特别小心。
- #{}:预编译方式,将传入的数据当作字符串处理,在 SQL 语句中会用占位符
- Dubbo 服务调用流程:
- 服务注册:服务提供者启动时,将自己提供的服务注册到注册中心(如 Zookeeper),注册中心保存服务的元数据信息,包括服务接口、地址等。
- 服务订阅:服务消费者启动时,从注册中心订阅自己需要的服务,注册中心返回服务提供者的地址列表。
- 服务调用:消费者根据负载均衡算法从地址列表中选择一个服务提供者进行调用。调用过程中,Dubbo 会进行网络通信,将请求发送到服务提供者,服务提供者处理请求并返回结果。
- RabbitMQ 保证消息不丢失:
- 生产者确认机制:生产者发送消息后,RabbitMQ 会返回确认信息给生产者,确认消息已接收。生产者可以通过
channel.confirmSelect()开启确认模式,然后通过channel.waitForConfirms()等待确认。 - 消息持久化:将消息和队列都设置为持久化。消息持久化通过设置
MessageProperties.PERSISTENT_TEXT_PLAIN实现,队列持久化通过声明队列时设置durable = true实现。这样在 RabbitMQ 重启后,消息和队列不会丢失。 - 消费者确认机制:消费者接收消息后,手动发送确认消息给 RabbitMQ,RabbitMQ 才会从队列中删除消息。可以通过设置
autoAck = false关闭自动确认,然后在处理完消息后调用channel.basicAck(deliveryTag, false)发送确认。
- 生产者确认机制:生产者发送消息后,RabbitMQ 会返回确认信息给生产者,确认消息已接收。生产者可以通过
- xxl - job 调度任务执行失败排查:
- 查看日志:xxl - job 有任务执行日志,查看日志可以了解任务执行过程中的详细信息,如异常堆栈信息,确定是代码逻辑错误还是运行时环境问题。
- 检查任务配置:确认任务的参数配置是否正确,如执行时间、执行频率、任务参数等。
- 检查依赖服务:如果任务依赖其他服务,如数据库、Redis 等,检查这些服务是否正常运行,网络是否畅通。
- 代码审查:对任务执行的代码进行审查,检查是否有逻辑错误、资源未释放等问题。