第一轮面试 面试官:首先问个基础的,Java中ArrayList和HashMap的底层数据结构分别是什么? 王铁牛:ArrayList底层是数组,HashMap底层是数组加链表,JDK 1.8 之后还有红黑树。 面试官:不错,回答得很准确。那HashMap在什么情况下会发生扩容? 王铁牛:当HashMap中的元素个数达到负载因子(默认0.75)乘以当前容量的时候,就会发生扩容。 面试官:很好。那说说ArrayList在添加大量元素时,怎样优化性能? 王铁牛:可以在添加元素前,通过构造函数指定合适的初始容量,减少扩容次数。
第二轮面试 面试官:接下来聊聊多线程和线程池。线程池有哪些核心参数? 王铁牛:有核心线程数、最大线程数、存活时间、时间单位,还有任务队列。 面试官:嗯,回答得不错。那线程池的拒绝策略有哪些? 王铁牛:有AbortPolicy,直接抛出异常;还有CallerRunsPolicy,把任务回退给调用者执行。 面试官:还行。那在高并发场景下,线程池该如何合理配置参数? 王铁牛:嗯……就是根据任务类型和系统资源来配吧,具体怎么配我一下子说不太清楚。
第三轮面试 面试官:最后问问框架相关的。Spring的IOC和AOP是什么? 王铁牛:IOC是控制反转,把对象创建和管理交给Spring容器;AOP是面向切面编程,能在不修改原有代码的情况下增加功能。 面试官:好。那Spring Boot有什么优势,能快速搭建项目的原理是什么? 王铁牛:优势就是简化配置,原理嘛……好像是有自动配置啥的,具体不太好说。 面试官:那Dubbo的服务调用流程是怎样的? 王铁牛:嗯……就是服务提供者注册服务,消费者去调用,中间好像还有注册中心啥的,细节不太记得了。
面试官:好的,今天的面试就到这里。你回去等通知吧,我们会综合评估所有候选人后,再决定是否录用你。感谢你今天来参加面试。
问题答案:
- ArrayList和HashMap的底层数据结构:
- ArrayList:底层是动态数组。它允许我们以数组的方式顺序存储元素,并且可以根据需要动态扩展容量。例如,当我们创建一个ArrayList时,它会先有一个默认的初始容量(通常是10),当添加的元素数量超过当前容量时,会进行扩容,一般是扩容为原来容量的1.5倍。
- HashMap:JDK 1.8之前,底层是数组加链表。数组的每个位置是一个链表的头节点,当发生哈希冲突时,新的元素会以链表的形式存储在该位置。JDK 1.8之后,当链表长度达到8且数组容量大于等于64时,链表会转化为红黑树,以提高查找效率。
- HashMap扩容机制:
- HashMap有两个重要参数,容量(capacity)和负载因子(loadFactor)。默认负载因子是0.75,当HashMap中的元素个数(size)达到负载因子乘以当前容量(capacity * loadFactor)时,就会触发扩容。扩容时,会创建一个新的更大的数组,然后将原数组中的元素重新计算哈希值并放入新数组中。例如,原容量为16,负载因子0.75,当元素个数达到16 * 0.75 = 12时,就会扩容。
- ArrayList添加大量元素性能优化:
- 在添加大量元素前,通过构造函数指定合适的初始容量。比如,如果我们知道要添加1000个元素,就可以创建
ArrayList list = new ArrayList(1000);这样就避免了多次扩容带来的性能开销。因为每次扩容都需要重新分配内存、复制原数组元素到新数组,这是比较耗时的操作。
- 在添加大量元素前,通过构造函数指定合适的初始容量。比如,如果我们知道要添加1000个元素,就可以创建
- 线程池核心参数:
- 核心线程数(corePoolSize):线程池中会一直存活的线程数,即使这些线程处于空闲状态,也不会被销毁,除非设置了
allowCoreThreadTimeOut为true。 - 最大线程数(maximumPoolSize):线程池中允许存在的最大线程数。当任务队列已满且活动线程数小于最大线程数时,线程池会创建新的线程来处理任务。
- 存活时间(keepAliveTime):当线程数大于核心线程数时,多余的空闲线程存活的最长时间。超过这个时间,多余的线程会被销毁。
- 时间单位(unit):存活时间的单位,如
TimeUnit.SECONDS表示秒。 - 任务队列(workQueue):用于存放等待执行的任务。常见的任务队列有
ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列,默认容量Integer.MAX_VALUE)等。
- 核心线程数(corePoolSize):线程池中会一直存活的线程数,即使这些线程处于空闲状态,也不会被销毁,除非设置了
- 线程池拒绝策略:
- AbortPolicy:这是默认的拒绝策略,当任务无法提交到线程池(队列已满且线程数达到最大线程数)时,直接抛出
RejectedExecutionException异常。 - CallerRunsPolicy:当任务无法提交到线程池时,该策略会将任务回退给调用者,由调用者所在的线程来执行该任务。这样可以降低新任务的提交速度,缓解线程池的压力。
- DiscardPolicy:当任务无法提交到线程池时,直接丢弃该任务,不做任何处理。
- DiscardOldestPolicy:当任务无法提交到线程池时,会丢弃任务队列中最老的一个任务(即队列头部的任务),然后尝试提交当前任务。
- AbortPolicy:这是默认的拒绝策略,当任务无法提交到线程池(队列已满且线程数达到最大线程数)时,直接抛出
- 高并发场景下线程池参数配置:
- 任务类型:如果是CPU密集型任务,核心线程数一般设置为
CPU核心数 + 1,因为CPU密集型任务主要消耗CPU资源,过多的线程会导致线程上下文切换开销增大。例如,在一个4核CPU的机器上,核心线程数可以设置为5。如果是I/O密集型任务,核心线程数可以设置得大一些,因为I/O操作会使线程处于等待状态,此时可以利用更多的线程来提高系统利用率,一般可以设置为2 * CPU核心数。 - 系统资源:要考虑服务器的内存、CPU等资源。如果服务器内存有限,任务队列不能设置过大,否则可能导致内存溢出。同时,最大线程数也不能设置过大,避免过多线程竞争资源导致系统性能下降。
- 任务类型:如果是CPU密集型任务,核心线程数一般设置为
- Spring的IOC和AOP:
- IOC(控制反转):传统的Java开发中,对象的创建和管理由开发者自己负责,而在Spring中,通过IOC容器来创建和管理对象。例如,我们定义一个
UserService类,在Spring中可以通过配置文件或注解的方式,让Spring容器来创建UserService的实例,而不是在代码中手动new UserService()。这样做的好处是降低了组件之间的耦合度,提高了代码的可维护性和可测试性。 - AOP(面向切面编程):它是一种编程范式,用于将横切关注点(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来。以日志记录为例,在传统的开发中,我们可能需要在每个业务方法中手动添加日志记录代码,而使用AOP,我们可以定义一个切面,在切面中编写日志记录逻辑,然后通过配置将这个切面应用到需要记录日志的方法上,这样就可以在不修改业务方法代码的情况下,为其添加日志记录功能。
- IOC(控制反转):传统的Java开发中,对象的创建和管理由开发者自己负责,而在Spring中,通过IOC容器来创建和管理对象。例如,我们定义一个
- Spring Boot优势及快速搭建项目原理:
- 优势:
- 简化配置:Spring Boot通过自动配置机制,大大减少了Spring项目中繁琐的XML配置或Java配置。例如,在Spring Boot中,我们只需要引入相关的依赖,就可以快速搭建一个Web应用,而不需要像传统Spring项目那样配置大量的Servlet、过滤器等。
- 快速开发:内置了Tomcat、Jetty等服务器,可直接打包成可执行的jar或war文件,方便部署。同时,它提供了丰富的Starter依赖,开发者只需要引入相应的Starter,就可以快速集成各种功能,如数据库访问、消息队列等。
- 原理:
- 自动配置:Spring Boot利用了Spring的条件化配置(
@Conditional)机制。它会根据项目中引入的依赖,自动配置相应的组件。例如,当引入spring - boot - starter - web依赖时,Spring Boot会自动配置Spring MVC相关的组件,包括DispatcherServlet、视图解析器等。 - Starter依赖:Starter是一种特殊的Maven或Gradle依赖,它聚合了一系列相关的依赖。比如
spring - boot - starter - jdbc,它不仅包含了JDBC相关的依赖,还会自动配置数据源、JdbcTemplate等组件,方便开发者进行数据库操作。
- 自动配置:Spring Boot利用了Spring的条件化配置(
- 优势:
- Dubbo服务调用流程:
- 服务注册:服务提供者启动时,会将自己提供的服务注册到注册中心(如Zookeeper)。它会向注册中心发送注册请求,包含服务的接口、实现类、版本等信息。
- 服务订阅:服务消费者启动时,会从注册中心订阅自己需要的服务。注册中心会将服务提供者的地址列表返回给消费者。
- 服务调用:消费者根据从注册中心获取的服务提供者地址列表,选择一个地址(可以通过负载均衡算法)发起远程调用。Dubbo支持多种远程通信协议,如Dubbo协议、HTTP协议等。在调用过程中,可能还会涉及到集群容错、负载均衡等机制,以保证服务调用的可靠性和性能。例如,当某个服务提供者出现故障时,Dubbo可以通过集群容错机制自动切换到其他可用的服务提供者。