互联网大厂Java面试大挑战:核心知识与实战场景全解析
面试官:请简要介绍一下Java核心知识中面向对象的三大特性。
王铁牛:封装、继承、多态。封装就是把数据和操作数据的方法封装在一起;继承是子类继承父类的属性和方法;多态就是同一个行为具有多个不同表现形式或形态。
面试官:不错,回答得很准确。那说说HashMap的底层数据结构。
王铁牛:HashMap底层是数组+链表+红黑树。当链表长度大于8且数组长度大于64时,链表会转换为红黑树。
面试官:嗯,理解得还行。再问个多线程的问题,如何创建一个线程?
王铁牛:可以继承Thread类,重写run方法;或者实现Runnable接口,实现run方法;还有通过Callable和FutureTask来创建线程。
第一轮结束。
面试官:谈谈Spring中IoC和AOP的概念。
王铁牛:IoC就是控制反转,把对象的创建和依赖注入交给Spring容器;AOP是面向切面编程,通过动态代理在不修改原有代码的基础上增强功能。
面试官:那Spring Boot有什么优势?
王铁牛:它简化了Spring应用的搭建和开发,内置了很多常用的配置,能快速构建项目。
面试官:说说MyBatis的缓存机制。
王铁牛:有一级缓存和二级缓存。一级缓存是SqlSession级别的,二级缓存是Mapper级别的。
第二轮结束。
面试官:讲讲JUC里的线程池,如何合理配置线程池参数?
王铁牛:线程池参数有corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。要根据任务的类型和数量等合理配置。
面试官:Dubbo的集群容错策略有哪些?
王铁牛:有Failover、Failfast、Failsafe、Failback、Forking。
面试官:RabbitMq的消息确认机制了解吗?
王铁牛:有发送确认和接收确认。发送确认又分普通确认、批量确认、异步确认。
第三轮结束。
面试官:今天的面试就到这里,回去等通知吧。
面试结束总结:本次面试围绕Java核心知识、JUC、JVM、多线程、线程池、HashMap、ArrayList、Spring、SpringBoot、MyBatis、Dubbo、RabbitMq等多个重要知识点展开。王铁牛在简单问题上回答得较为准确,展现了一定的基础。但在复杂问题上表现欠佳,回答不够清晰全面。对于Java核心知识中的面向对象特性、HashMap底层结构、多线程创建方式等基础问题回答正确,说明有一定的理论基础。然而在Spring、MyBatis、JUC等相关复杂问题上,回答的深度和准确性还有待提高。整体来看,需要进一步加强对各个知识点的深入理解和综合运用能力,以便更好地应对互联网大厂的面试挑战。
答案:
- Java面向对象三大特性:
- 封装:将数据和操作数据的方法封装在一起,对外提供统一的接口。这样可以隐藏内部实现细节,提高代码的安全性和可维护性。例如,一个类中的成员变量可以通过private修饰,然后通过public的getter和setter方法来访问和修改,外部类只能通过这些公开的方法来操作该类的内部数据。
- 继承:子类继承父类的属性和方法,实现代码的复用。子类可以拥有父类的非private成员,并且可以重写父类的方法来实现自己的特殊逻辑。比如,定义一个父类Animal,子类Dog可以继承Animal,拥有Animal的一些通用属性(如name、age)和方法(如eat),同时Dog类还可以根据自身特点重写eat方法。
- 多态:同一个行为具有多个不同表现形式或形态。多态分为编译时多态(方法重载)和运行时多态(方法重写)。运行时多态的实现依赖于对象的动态绑定,即根据对象的实际类型来调用相应的方法。例如,定义一个父类Shape,子类Circle和Rectangle都继承自Shape并重写draw方法,当调用Shape s = new Circle(); s.draw(); 时,实际调用的是Circle类的draw方法。
- HashMap底层数据结构:
- HashMap底层是数组+链表+红黑树。当向HashMap中插入元素时,首先根据key的hash值计算出在数组中的索引位置。
- 如果该位置为空,则直接插入新节点。
- 如果该位置不为空,则会遍历链表或红黑树。若链表长度小于8且数组长度大于64时,新节点会插入到链表尾部;当链表长度大于8且数组长度大于64时,链表会转换为红黑树,新节点会插入到红黑树中。这样做的目的是为了提高查找、插入和删除操作的效率。当红黑树节点数量小于等于6时,又会转换回链表结构。
- 创建线程的方式:
- 继承Thread类:定义一个类继承Thread类,并重写run方法。然后通过创建该类的实例来启动线程,调用start方法,start方法会调用run方法执行线程体。例如:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
MyThread thread = new MyThread();
thread.start();
- **实现Runnable接口**:定义一个类实现Runnable接口,实现run方法。然后通过Thread类的构造函数将该实例作为参数创建Thread对象,再调用start方法启动线程。例如:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable is running");
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
- **通过Callable和FutureTask**:Callable接口中的call方法有返回值,通过FutureTask类来包装Callable任务,FutureTask实现了Runnable接口,然后可以将FutureTask作为参数传递给Thread来创建线程并启动。获取线程执行结果可以通过FutureTask的get方法。例如:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
- Spring中IoC和AOP的概念:
- IoC(控制反转):传统的应用程序中,对象的创建和依赖关系由应用程序自身管理。而在Spring中,IoC将对象的创建和依赖注入交给Spring容器来管理。这样做的好处是降低了对象之间的耦合度,提高了代码的可维护性和可测试性。例如,一个Service类依赖于一个Dao类,在Spring中,可以通过配置文件或注解将Dao类的实例注入到Service类中,而不是在Service类内部直接创建Dao对象。
- AOP(面向切面编程):AOP通过动态代理在不修改原有代码的基础上增强功能。它将业务逻辑的各个关注点(如日志记录、事务管理、权限验证等)分离出来,形成一个个切面。在程序运行时,这些切面会织入到目标对象的方法执行过程中。比如,通过AOP可以在方法执行前记录日志,方法执行后进行事务提交等操作,而不需要在每个业务方法中都重复编写这些代码。
- Spring Boot的优势:
- 简化项目搭建:Spring Boot提供了一系列的starter依赖,通过引入这些starter可以快速集成各种常用的功能,如Web、数据库访问等,无需手动配置大量繁琐的依赖和配置文件。
- 内置大量配置:它内置了许多默认的配置,能够快速搭建一个可用的应用程序。例如,默认配置了Tomcat服务器、自动配置了数据源等,开发人员可以专注于业务逻辑的实现,而不需要花费大量时间在基础配置上。
- 快速构建项目:支持多种构建工具(如Maven、Gradle),可以快速创建一个Spring Boot项目的骨架,然后在这个基础上进行业务功能的开发,大大提高了开发效率。
- MyBatis的缓存机制:
- 一级缓存:是SqlSession级别的缓存。当执行同一个SqlSession中的查询时,如果缓存中已经存在该查询结果,则直接从缓存中获取,不会再次查询数据库。一级缓存的生命周期与SqlSession一致,当SqlSession关闭时,一级缓存也会被清空。例如,在同一个SqlSession中多次执行相同的查询语句,只会执行一次数据库查询,后续结果从缓存中获取。
- 二级缓存:是Mapper级别的缓存。多个SqlSession可以共享二级缓存。当一个Mapper的查询在二级缓存中命中时,会直接返回缓存中的数据。二级缓存的开启需要在MyBatis的配置文件中进行相应配置,并且要求Mapper对应的POJO类必须实现Serializable接口。比如,多个不同的SqlSession查询同一个Mapper的相同数据时,如果二级缓存中有,则都可以从缓存中获取。
- JUC里线程池参数及配置:
- corePoolSize:线程池的核心线程数。当提交的任务数小于corePoolSize时,线程池会创建新的线程来执行任务。
- maximumPoolSize:线程池允许的最大线程数。当提交的任务数大于corePoolSize且任务队列已满时,会创建新的线程执行任务,但线程数不能超过maximumPoolSize。
- keepAliveTime:线程池中的线程在空闲时的存活时间。当线程空闲时间超过keepAliveTime时,非核心线程会被销毁。
- unit:keepAliveTime的时间单位。
- workQueue:任务队列,用于存放提交的任务。当提交的任务数大于corePoolSize时,任务会被放入workQueue中。常用的任务队列有ArrayBlockingQueue、LinkedBlockingQueue等。
- threadFactory:用于创建线程的工厂,可以自定义线程的名称、优先级等属性。
- handler:当线程池的线程数达到maximumPoolSize且任务队列已满时,新提交的任务会由handler来处理。常见的handler有ThreadPoolExecutor.AbortPolicy(抛出异常)、ThreadPoolExecutor.CallerRunsPolicy(调用者运行)、ThreadPoolExecutor.DiscardPolicy(丢弃任务)、ThreadPoolExecutor.DiscardOldestPolicy(丢弃最旧的任务)。
- 合理配置:如果任务主要是CPU密集型,corePoolSize可以设置为CPU核心数+1,避免过多线程上下文切换带来的开销;如果任务是I/O密集型,可以适当增大corePoolSize和maximumPoolSize,根据实际情况调整keepAliveTime和workQueue的大小,以充分利用系统资源并提高线程池的性能。
- Dubbo的集群容错策略:
- Failover:失败自动切换,当调用出现失败时,会自动重试其他服务器。默认重试次数是2次,可以通过配置修改。例如,调用服务时,如果第一次调用失败,会自动重试其他可用的服务实例。
- Failfast:快速失败,只调用一次,失败立即报错。适用于幂等操作,如查询操作。如果调用过程中出现失败,不会进行重试。
- Failsafe:失败安全,出现异常时直接忽略,不抛出异常。常用于写一些无关紧要的操作,如记录日志等。即使操作失败,也不会影响整体业务流程。
- Failback:失败自动恢复,调用失败后,会在后台异步重试。适用于对实时性要求不高的操作,如数据同步等。
- Forking:并行调用多个服务器,只要有一个成功就返回。通过配置可以指定并行调用的服务器数量。例如,同时调用多个服务实例,哪个先返回成功结果就采用哪个结果。
- RabbitMq的消息确认机制:
- 发送确认:
- 普通确认:生产者发送一条消息后,等待Broker返回确认结果。如果确认成功,表示消息已成功发送到Broker;如果失败,生产者可以根据情况进行重发等操作。
- 批量确认:生产者可以一次性发送多条消息,然后等待Broker对这些消息的批量确认。这样可以减少网络开销。
- 异步确认:生产者通过注册监听器,当Broker返回确认结果时,监听器会接收到通知,从而进行相应处理。异步确认可以提高消息发送的效率,尤其是在发送大量消息时。
- 接收确认:消费者接收消息后,需要向Broker发送确认消息,表示已成功接收该消息。Broker接收到确认后,会将该消息从队列中移除。接收确认方式有自动确认和手动确认。自动确认是消费者接收到消息后,Broker自动认为该消息已被成功消费;手动确认则需要消费者在业务逻辑处理完成后,手动调用方法向Broker发送确认消息,这样可以保证消息在业务处理成功后才被真正消费。
- 发送确认: