第一轮面试 面试官:先问些基础的,Java 中 ArrayList 和 LinkedList 的区别是什么?
王铁牛:ArrayList 是基于数组实现的,随机访问快,LinkedList 基于链表实现,插入和删除效率高,尤其是在中间位置操作时。
面试官:回答得不错。那 HashMap 在 JDK1.7 和 JDK1.8 中有什么主要区别?
王铁牛:1.8 中引入了红黑树,当链表长度超过阈值会转为红黑树,提高查找效率,1.7 只有链表。
面试官:很好。讲讲 Spring 中 IOC 是什么?
王铁牛:IOC 就是控制反转,把对象创建和管理的控制权交给 Spring 容器,这样代码耦合度降低。
第二轮面试 面试官:那 Spring Boot 相对于 Spring 有什么优势?
王铁牛:嗯……Spring Boot 能快速搭建项目,自动配置很多东西,减少了配置文件,开发更方便。
面试官:不错。MyBatis 中 #{} 和 ${} 的区别是什么?
王铁牛:#{} 是预编译处理,能防止 SQL 注入,${} 是字符串替换,可能有 SQL 注入风险。
面试官:很好。Dubbo 是什么,主要解决什么问题?
王铁牛:Dubbo 是分布式服务框架,解决了服务治理问题,像服务注册、发现、调用这些。
第三轮面试 面试官:RabbitMQ 中的消息确认机制是怎么回事?
王铁牛:呃……就是消息发送出去后,要确认有没有成功到达队列,好像有个什么 confirm 模式。
面试官:那 Redis 如何实现分布式锁?
王铁牛:用 setnx 命令,设置一个 key - value,如果设置成功就拿到锁,不过具体细节我不太清楚。
面试官:xxl - job 是什么,在项目中有什么应用场景?
王铁牛:它是个分布式任务调度平台,能做定时任务,比如定时数据清理啥的。
面试官:好的,今天的面试就到这里。你对自己今天的表现整体还算满意,基础知识掌握得不错,但在一些进阶知识点上还可以再深入理解。回去等通知吧,我们会综合评估所有候选人后,尽快给你答复。
问题答案:
- ArrayList 和 LinkedList 的区别:
- 数据结构:ArrayList 基于动态数组,LinkedList 基于双向链表。
- 随机访问:ArrayList 支持随机访问,通过索引直接定位元素,时间复杂度为 O(1);LinkedList 随机访问效率低,需要从头或尾遍历,时间复杂度为 O(n)。
- 插入和删除:在 ArrayList 中间插入或删除元素,需要移动大量元素,时间复杂度为 O(n);LinkedList 在中间插入或删除元素只需修改指针,时间复杂度为 O(1)。但如果是在末尾操作,ArrayList 效率也很高,因为动态数组有扩容机制。
- HashMap 在 JDK1.7 和 JDK1.8 中的区别:
- 数据结构:JDK1.7 采用数组 + 链表结构,JDK1.8 采用数组 + 链表 + 红黑树结构。当链表长度大于阈值(默认为 8)且数组长度大于 64 时,链表会转化为红黑树,以提高查找效率。
- 插入方式:JDK1.7 采用头插法,新元素插入到链表头部;JDK1.8 采用尾插法,新元素插入到链表尾部,避免了多线程环境下头插法可能导致的环形链表问题。
- 扩容机制:JDK1.7 扩容时,需要重新计算每个元素在新数组中的位置并重新插入;JDK1.8 优化了扩容算法,部分元素在扩容时位置不变,减少了重新计算和插入的开销。
- Spring 中 IOC:
- 概念:控制反转(Inversion of Control),是一种设计思想。传统应用程序中,对象创建和依赖关系管理由应用程序自身负责,耦合度高。在 Spring 中,IOC 把对象创建和管理的控制权交给 Spring 容器,应用程序只需要使用对象,降低了代码耦合度。
- 实现方式:通过依赖注入(Dependency Injection,DI)实现,常见的注入方式有构造函数注入、Setter 方法注入和接口注入。例如构造函数注入,在类的构造函数中传入依赖对象;Setter 方法注入,通过类的 Setter 方法设置依赖对象。
- Spring Boot 相对于 Spring 的优势:
- 快速搭建:Spring Boot 提供了大量的 Starter 依赖,能快速搭建基于 Spring 的项目,减少了项目初始化的配置工作。例如引入 spring - boot - starter - web 依赖,就能快速搭建一个 Web 项目。
- 自动配置:Spring Boot 能根据项目依赖自动配置 Spring 应用,无需手动编写大量配置文件。比如引入 MySQL 依赖后,Spring Boot 能自动配置数据源等相关内容。
- 生产就绪:Spring Boot 内置了监控、健康检查等功能,方便项目在生产环境中的部署和运维。
- MyBatis 中 #{} 和 ${} 的区别:
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为?,然后使用 PreparedStatement 进行设置参数值,能有效防止 SQL 注入。例如 SQL 语句为
select * from user where username = #{username},实际执行时会变为select * from user where username =?,然后通过 PreparedStatement 设置 username 的值。 - **{} 时,会直接将 {username}'
,如果 username 变量被恶意赋值为' or '1' = '1`,就会导致 SQL 注入。
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为?,然后使用 PreparedStatement 进行设置参数值,能有效防止 SQL 注入。例如 SQL 语句为
- Dubbo:
- 概念:是阿里巴巴开源的高性能、轻量级的分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。
- 解决问题:
- 服务注册与发现:Dubbo 提供了服务注册中心(如 Zookeeper),服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务地址,实现服务的动态发现。
- 负载均衡:Dubbo 支持多种负载均衡策略,如随机、轮询、最少活跃调用数等,在多个服务提供者之间合理分配请求,提高系统的整体性能。
- 服务治理:包括服务监控、服务降级、服务容错等功能,保障分布式系统的稳定性和可靠性。
- RabbitMQ 中的消息确认机制:
- 生产者确认(Publisher Confirm):
- confirm 模式:生产者将信道设置为 confirm 模式后,所有在该信道上发布的消息都会被分配一个唯一的 ID。当消息被成功投递到所有匹配的队列后,RabbitMQ 会发送一个确认(Basic.Ack)给生产者,包含消息的唯一 ID。如果消息投递失败,RabbitMQ 会发送一个否定确认(Basic.Nack),生产者可以根据这个确认结果进行相应处理,如重发消息。
- return 模式:当消息无法路由到任何一个匹配的队列时,RabbitMQ 会将消息返回给生产者。生产者需要在发送消息时设置 mandatory 标志为 true,并添加 ReturnListener 来接收返回的消息。
- 消费者确认(Consumer Acknowledge):消费者从队列中获取消息后,需要向 RabbitMQ 发送确认消息(Basic.Ack),表示已经成功处理了该消息。RabbitMQ 在收到确认后,才会将该消息从队列中删除。如果消费者在处理消息过程中出现异常,没有发送确认消息,RabbitMQ 会认为消息没有被成功处理,可能会重新将消息发送给其他消费者或重新放入队列。
- 生产者确认(Publisher Confirm):
- Redis 实现分布式锁:
- 使用 setnx 命令:
SETNX key value,如果 key 不存在,则设置 key 的值为 value,返回 1;如果 key 已存在,则不做任何操作,返回 0。利用这个特性,多个客户端同时尝试设置同一个 key,只有一个客户端能成功,即获取到锁。例如:Jedis jedis = new Jedis("localhost"); String lockKey = "myLock"; String requestId = UUID.randomUUID().toString(); if ("OK".equals(jedis.set(lockKey, requestId, "NX", "EX", 10))) { // 获取锁成功,执行业务逻辑 try { // 业务代码 } finally { // 释放锁 if (requestId.equals(jedis.get(lockKey))) { jedis.del(lockKey); } } } else { // 获取锁失败,可选择重试或其他处理 } - 存在问题及解决:
- 锁超时问题:如果获取锁的客户端在锁过期前没有完成业务逻辑,其他客户端可能会获取到锁,导致数据不一致。可以通过设置合理的锁过期时间,或者在业务逻辑中延长锁的有效期来解决。
- 误删锁问题:如上述代码,在释放锁时,需要验证当前锁的值是否为自己设置的值,避免误删其他客户端获取的锁。
- 使用 setnx 命令:
- xxl - job:
- 概念:是一个轻量级分布式任务调度平台,由大众点评员工徐雪里开源。它提供了简单易用的任务管理界面,支持任务的调度、执行、监控等功能。
- 应用场景:
- 定时任务:如定时数据清理、定时报表生成等。例如每天凌晨 2 点清理数据库中一周前的日志数据。
- 分布式任务:当任务量较大,单机无法满足处理需求时,可以将任务分发到多个节点并行处理。例如对海量数据进行计算,可将数据分片,每个节点处理一部分数据。
- 任务依赖:支持任务之间的依赖关系,比如任务 B 需要在任务 A 成功执行后才能执行。