在互联网大厂的Java面试现场,面试官正严肃地考察一位求职者。
面试官:第一轮开始。第一个问题,Java中ArrayList和HashMap的底层数据结构分别是什么? 王铁牛:ArrayList底层是数组,HashMap底层是数组加链表,JDK1.8后引入了红黑树。 面试官:回答不错。第二个问题,HashMap在什么情况下会发生扩容? 王铁牛:当HashMap中的元素个数达到负载因子(默认0.75)乘以当前容量时,就会进行扩容。 面试官:很好。第三个问题,ArrayList在添加元素时,如果容量不够会怎样? 王铁牛:它会进行扩容,新容量大概是原容量的1.5倍。
面试官:第二轮。第一个问题,多线程中线程池的核心参数有哪些,分别代表什么含义? 王铁牛:呃……有核心线程数,就是一直存活的线程数,还有最大线程数,就是最多能创建的线程数。其他的……我不太确定。 面试官:好。第二个问题,JUC包下的CountDownLatch的作用是什么? 王铁牛:好像是用来控制线程等待的,具体怎么用不太清楚。 面试官:第三个问题,在多线程环境下,HashMap为什么不安全,如何解决? 王铁牛:因为多线程操作可能会导致数据不一致,解决办法……好像可以用ConcurrentHashMap。
面试官:第三轮。第一个问题,Spring框架中IOC和AOP的原理是什么? 王铁牛:IOC是控制反转,就是把对象创建和管理交给Spring容器。AOP嘛,是面向切面编程,用来处理一些横切关注点,具体原理说不太清。 面试官:第二个问题,Spring Boot如何实现自动配置的? 王铁牛:嗯……好像是通过一些配置类和注解,具体不太明白。 面试官:第三个问题,MyBatis中#{}和{}的区别是什么? **王铁牛**:#{}是预编译处理,{}是字符串替换,#{}更安全,能防止SQL注入。 面试官:第四个问题,Dubbo的服务调用流程是怎样的? 王铁牛:呃,就是服务提供者注册服务,消费者从注册中心获取服务,然后调用,细节不太清楚。 面试官:最后一个问题,RabbitMQ在高并发场景下如何保证消息不丢失? 王铁牛:可以开启持久化,还有……其他的不太知道了。
面试官思索片刻后说道:“今天的面试就到这里,后续我们会综合评估,你回家等通知吧。我们会在一周内给你回复,无论结果如何,都感谢你今天来参加面试。希望你之后能继续提升自己的技术能力,对今天回答得不太好的知识点可以深入学习。”
答案:
- ArrayList和HashMap的底层数据结构:ArrayList底层是数组,便于快速随机访问。HashMap在JDK1.8前底层是数组加链表,JDK1.8后,当链表长度大于8且数组容量大于64时,链表会转化为红黑树,以提高查找效率。
- HashMap扩容机制:HashMap有负载因子(默认0.75),当元素个数达到负载因子乘以当前容量时,就会扩容。新容量是原容量的2倍,扩容时会重新计算元素在新数组中的位置。
- ArrayList容量不足时的处理:ArrayList容量不足时,会创建一个新数组,新容量为原容量的1.5倍(原容量右移一位再加原容量),然后将原数组元素复制到新数组。
- 线程池核心参数:
- 核心线程数(corePoolSize):线程池长期维持的线程数,即使这些线程处于空闲状态,也不会被销毁。
- 最大线程数(maximumPoolSize):线程池所能容纳的最大线程数。当活动线程数达到这个数值后,后续任务会被阻塞。
- 队列容量(workQueue):用于存放等待执行任务的队列,如ArrayBlockingQueue、LinkedBlockingQueue等。
- 存活时间(keepAliveTime):当线程数大于核心线程数时,多余的空闲线程的存活时间。即超过这个时间,多余线程会被销毁。
- 时间单位(unit):存活时间的单位,如TimeUnit.SECONDS。
- CountDownLatch作用:CountDownLatch是JUC包下的一个同步工具类,它允许一个或多个线程等待,直到其他线程完成一组操作。通过构造函数传入一个初始计数值,每个线程完成任务后调用countDown()方法使计数值减一,等待的线程调用await()方法,直到计数值为0才继续执行。
- HashMap在多线程下不安全及解决办法:多线程环境下,HashMap不安全主要体现在扩容时可能会形成循环链表,导致死循环,以及数据覆盖问题。解决办法可以使用ConcurrentHashMap,它采用分段锁机制,允许多个线程同时访问不同段的数据,提高并发性能。
- Spring中IOC和AOP原理:
- IOC(控制反转):把对象的创建和管理从应用程序代码转移到Spring容器中。Spring容器通过读取配置文件(XML或注解),实例化对象并注入依赖。例如,使用@Component注解将一个类标识为Spring管理的组件,Spring会自动创建其实例。
- AOP(面向切面编程):将横切关注点(如日志记录、事务管理等)从业务逻辑中分离出来,形成独立的切面。Spring通过动态代理(JDK动态代理或CGLIB代理)在目标方法执行前后织入切面逻辑。例如,使用@Aspect注解定义切面,@Before、@After等注解定义切点和增强逻辑。
- Spring Boot自动配置原理:Spring Boot通过@SpringBootApplication注解开启自动配置,该注解包含@EnableAutoConfiguration。Spring Boot会扫描META - INF/spring.factories文件,里面定义了各种自动配置类,根据项目依赖和配置属性,自动配置类会按需生效,为应用程序提供默认配置。
- MyBatis中#{}和${}的区别:
- #{}:预编译处理,MyBatis会将SQL中的#{}替换为?,并使用PreparedStatement设置参数值,能有效防止SQL注入。
- ${}:字符串替换,直接将参数值嵌入SQL中,不进行预编译,存在SQL注入风险,一般用于传入表名、列名等。
- Dubbo服务调用流程:
- 服务注册:服务提供者启动时,将服务接口和实现类等信息注册到注册中心(如Zookeeper)。
- 服务订阅:服务消费者启动时,从注册中心订阅所需服务的地址列表。
- 服务调用:消费者根据负载均衡策略从地址列表中选择一个服务提供者进行调用,通过网络通信(如Netty)将请求发送给提供者,提供者处理请求并返回结果。
- RabbitMQ在高并发场景下保证消息不丢失:
- 生产者端:开启confirm模式,生产者发送消息后,RabbitMQ会返回确认信息,告知消息是否成功到达Broker。
- 队列端:将队列设置为持久化,即durable = true,这样RabbitMQ重启后队列依然存在。
- 消费者端:设置手动确认模式(acknowledgeMode = manual),消费者处理完消息后,手动向RabbitMQ发送确认消息,RabbitMQ才会删除该消息,否则会重新投递。