在一间明亮却略显严肃的面试房间里,一位求职者正襟危坐在桌前,对面的面试官表情沉稳,一场决定命运的互联网大厂Java面试即将拉开帷幕。
面试官:“第一轮提问开始。先说说Java中ArrayList和HashMap的区别。” 王铁牛:“ArrayList是有序的,按顺序存储元素,基于数组实现;HashMap是无序的,基于哈希表,通过键值对存储。” 面试官:“回答得不错。那HashMap在什么情况下会发生哈希冲突,怎么解决的?” 王铁牛:“呃……数据多了可能就冲突了吧,好像是用链表或者红黑树解决。” 面试官:“还算沾边。ArrayList扩容机制了解吗?” 王铁牛:“初始容量是10,当元素个数达到容量的0.75倍时,就会扩容,新容量是原来的1.5倍。” 面试官:“第一轮表现还行。接下来第二轮。讲讲Spring的IOC和AOP是什么?” 王铁牛:“IOC是控制反转,把对象创建和管理交给Spring容器;AOP是面向切面编程,能在不修改原有代码基础上增加功能。” 面试官:“不错。那Spring Boot自动配置原理能讲讲吗?” 王铁牛:“就是Spring Boot根据依赖自动配置一些组件,具体咋实现不太清楚。” 面试官:“嗯。MyBatis的一级缓存和二级缓存有啥区别?” 王铁牛:“一级缓存是SqlSession级别的,二级缓存是Mapper级别的,二级缓存能跨SqlSession。” 面试官:“第二轮也还可以。最后一轮。Dubbo的服务调用流程是怎样的?” 王铁牛:“好像是服务提供者注册服务,消费者去注册中心找服务,然后调用。” 面试官:“那RabbitMQ的消息确认机制了解吗?” 王铁牛:“有个生产者确认,还有个消费者确认,具体细节不太熟。” 面试官:“xxl - job的调度原理呢?” 王铁牛:“不太清楚,好像是有个调度中心啥的。” 面试官:“Redis的持久化方式有哪些?” 王铁牛:“RDB和AOF,RDB是快照,AOF是记录写操作。” 面试官:“今天的面试就到这里。整体来看,你对一些基础知识点掌握得还可以,但对于一些稍微深入的技术原理,理解得还不够透彻。我们后续会综合评估所有候选人,你回家等通知吧,无论结果如何,我们都会在一周内给你回复。感谢你今天来参加面试。”
问题答案:
- ArrayList和HashMap的区别:
- 存储结构:ArrayList基于数组,按顺序存储元素,有索引,可通过索引快速访问元素;HashMap基于哈希表,以键值对形式存储,通过哈希算法计算键的哈希值来确定存储位置,无序。
- 数据特点:ArrayList允许重复元素,HashMap键不能重复,值可以重复。
- 应用场景:ArrayList适合需要顺序访问、频繁插入删除操作少的场景;HashMap适合根据键快速查找值的场景。
- HashMap哈希冲突及解决:
- 哈希冲突原因:不同的键经过哈希算法计算后得到相同的哈希值。比如两个不同的字符串,由于哈希算法的局限性,可能算出一样的哈希值。
- 解决方法:
- 链地址法:JDK1.8之前主要采用,当发生哈希冲突时,在该哈希值对应的位置用链表存储冲突的元素。JDK1.8之后,当链表长度大于8且数组长度大于64时,链表会转化为红黑树,以提高查找效率。
- 开放定址法:当发生冲突时,通过某种探测算法在哈希表中寻找下一个空的位置来存储元素。
- ArrayList扩容机制:
- 初始容量:默认初始容量为10。
- 扩容触发条件:当添加元素时,若当前元素个数达到数组容量的0.75倍(即size >= capacity * 0.75),就会触发扩容。
- 扩容方式:新容量是原来容量的1.5倍(即newCapacity = oldCapacity + (oldCapacity >> 1)),然后将原数组内容复制到新数组。
- Spring的IOC和AOP:
- IOC(控制反转):
- 概念:将对象的创建和管理控制权从应用程序代码转移到Spring容器。比如在传统Java开发中,我们自己创建对象并管理其生命周期;而在Spring中,由Spring容器负责创建、配置和管理对象。
- 实现方式:通过依赖注入(DI),常见的有构造函数注入、Setter方法注入和接口注入。以构造函数注入为例,在类的构造函数中声明依赖对象,Spring容器在创建该类实例时,会自动将依赖对象注入进来。
- AOP(面向切面编程):
- 概念:将一些与业务逻辑无关但又贯穿多个业务模块的功能(如日志记录、事务管理、权限控制等)抽取出来,形成一个独立的切面,在不修改原有业务代码的基础上,将这些切面功能动态地织入到目标业务模块中。
- 实现方式:基于动态代理,有JDK动态代理和CGLIB动态代理。JDK动态代理基于接口实现,CGLIB动态代理基于继承实现,用于为没有实现接口的类创建代理。
- IOC(控制反转):
- Spring Boot自动配置原理:
- 核心机制:Spring Boot通过@EnableAutoConfiguration注解开启自动配置功能。它会根据项目中引入的依赖,在classpath下寻找META - INF/spring.factories文件,该文件中定义了各种自动配置类。例如,当引入spring - boot - starter - web依赖时,会自动配置Tomcat、Spring MVC等相关组件。Spring Boot会根据条件注解(如@ConditionalOnClass、@ConditionalOnProperty等)判断是否满足自动配置条件,只有满足条件才会进行配置。
- MyBatis的一级缓存和二级缓存区别:
- 一级缓存:
- 作用域:SqlSession级别,在同一个SqlSession内,执行相同的SQL查询时,MyBatis会先从一级缓存中查找结果,若有则直接返回,不再执行SQL。
- 生命周期:与SqlSession生命周期一致,当SqlSession关闭或提交事务时,一级缓存会被清空。
- 二级缓存:
- 作用域:Mapper级别,多个SqlSession可以共享二级缓存。不同SqlSession执行相同Mapper的SQL查询时,若二级缓存中有数据,则直接返回。
- 生命周期:应用程序运行期间,除非手动清空或配置了缓存刷新策略,否则二级缓存一直存在。需要注意的是,使用二级缓存时,缓存的对象必须实现Serializable接口。
- 一级缓存:
- Dubbo的服务调用流程:
- 服务注册:服务提供者启动时,将自己提供的服务注册到注册中心(如Zookeeper),注册中心记录服务提供者的地址等信息。
- 服务订阅:服务消费者启动时,向注册中心订阅自己所需的服务,注册中心返回服务提供者的地址列表。
- 服务调用:服务消费者从地址列表中选择一个服务提供者进行调用。调用过程中,Dubbo支持负载均衡策略(如随机、轮询等)选择具体的服务实例。若服务提供者出现故障,注册中心会感知并通知服务消费者,消费者会重新选择可用的服务提供者。
- RabbitMQ的消息确认机制:
- 生产者确认(Publisher Confirm):
- 开启方式:通过调用channel.confirmSelect()方法开启。
- 原理:生产者发送消息后,RabbitMQ会给生产者发送一个确认消息,告知消息是否成功到达Broker。如果消息成功到达,生产者会收到一个ACK确认;如果消息未成功到达,生产者会收到一个NACK确认,此时生产者可以选择重发消息等处理方式。
- 消费者确认(Consumer Ack):
- 开启方式:默认自动确认(auto - ack=true),但这种方式存在风险,若消费者在处理消息过程中宕机,消息可能丢失。所以通常设置为手动确认(auto - ack=false)。
- 原理:消费者接收并处理完消息后,向RabbitMQ发送一个确认消息(ACK),RabbitMQ收到ACK后才会将该消息从队列中删除。如果消费者未发送ACK或发送NACK,RabbitMQ会根据配置的策略(如重新入队等)处理该消息。
- 生产者确认(Publisher Confirm):
- xxl - job的调度原理:
- 调度中心:是xxl - job的核心组件,负责管理调度任务,包括任务的新增、修改、删除,以及触发任务调度。调度中心基于Quartz实现定时任务调度,通过数据库存储任务信息和调度日志。
- 执行器:部署在业务系统中,负责接收调度中心的调度请求并执行任务。执行器启动时会向调度中心注册自己,调度中心根据任务配置,定时向对应的执行器发送调度请求,执行器执行任务后将结果返回给调度中心。
- Redis的持久化方式:
- RDB(Redis Database):
- 原理:在指定的时间间隔内,将内存中的数据集快照写入磁盘,生成一个dump.rdb文件。恢复时,直接将rdb文件读入内存。
- 优点:适合大规模数据恢复,生成的文件紧凑,占用空间小。
- 缺点:可能会丢失最后一次快照后的所有数据,因为是定期快照。
- AOF(Append - Only - File):
- 原理:以日志的形式记录Redis服务器执行的写操作,恢复时重新执行这些写操作来重建数据集。
- 优点:数据安全性高,基本可以保证不丢失数据,因为是实时记录写操作。
- 缺点:文件体积可能较大,恢复速度相对RDB较慢,因为需要重放所有写操作。