《揭秘互联网大厂Java面试:从基础到进阶的核心知识点大考察》

45 阅读11分钟

在一个宽敞明亮却又弥漫着紧张气息的面试房间里,一场决定求职者命运的互联网大厂Java面试正在进行。

面试官:“第一轮面试开始。先问基础的,Java中ArrayList和HashMap的底层数据结构分别是什么?”

王铁牛:“ArrayList底层是数组,HashMap底层是数组加链表,JDK1.8 后引入了红黑树。”

面试官:“回答得不错。那ArrayList在扩容时具体是怎么操作的?”

王铁牛:“当元素个数超过容量的一定比例,好像是0.75,就会进行扩容,新容量是原来的1.5倍,然后把旧数组的元素复制到新数组。”

面试官:“很好。HashMap在JDK1.8中,链表转红黑树的条件是什么?”

王铁牛:“当链表长度达到8,并且数组容量大于等于64时,链表就会转成红黑树。”

面试官:“第二轮。谈谈Spring框架中IOC和AOP的概念。”

王铁牛:“IOC就是控制反转,把对象的创建和管理交给Spring容器。AOP是面向切面编程,能把一些通用功能,像日志、事务,切到业务逻辑中。”

面试官:“那Spring是如何实现AOP的?”

王铁牛:“嗯……好像是通过动态代理,有JDK动态代理和CGLIB代理,具体怎么选我不太清楚了。”

面试官:“Spring Boot有哪些核心特性,让它能快速开发?”

王铁牛:“它有自动配置,能根据依赖自动配置一些组件,还有起步依赖,引入相关依赖很方便。”

面试官:“第三轮。MyBatis中#{}和${}的区别是什么?”

王铁牛:“#{}是预编译,能防止SQL注入,${}是直接替换,不安全。”

面试官:“Dubbo的服务调用流程是怎样的?”

王铁牛:“嗯……就是服务提供者注册服务,消费者从注册中心获取服务,然后调用,具体细节我有点模糊。”

面试官:“RabbitMQ有哪些常见的应用场景?”

王铁牛:“可以用于异步处理,像订单下单后发消息通知其他系统,还有削峰填谷,处理高并发。”

面试官:“最后问个xxl - job的问题,它是如何实现分布式任务调度的?”

王铁牛:“这个……我不太了解。”

面试官:“好的,面试就到这里。你对今天的面试整体表现还算不错,基础部分掌握得比较扎实,但在一些进阶和深入的知识点上还需要加强。回去等我们的通知吧,无论结果如何,希望你在技术学习上继续努力。”

答案:

  1. ArrayList和HashMap的底层数据结构
    • ArrayList:底层是数组结构,它允许以数组下标的方式快速访问元素。例如,list.get(0)可以直接获取到第一个元素,时间复杂度为O(1)。数组的特点是连续内存存储,所以查询效率高,但插入和删除元素可能涉及数组的移动,效率相对较低。
    • HashMap:JDK1.7及之前,底层是数组加链表。数组的每个位置是一个链表头,通过哈希算法计算键的哈希值,然后对数组长度取模得到数组下标,将键值对存储在对应下标的链表中。这种结构在哈希冲突较多时,链表会变长,查询效率会降低,时间复杂度最坏为O(n)。JDK1.8引入了红黑树,当链表长度达到8且数组容量大于等于64时,链表会转换为红黑树,红黑树的查询、插入、删除时间复杂度为O(logn),提高了哈希冲突较多时的性能。
  2. ArrayList扩容操作
    • ArrayList有一个默认初始容量,通常是10。当向ArrayList中添加元素时,会检查当前元素个数是否超过了容量。如果超过了容量的0.75倍(负载因子默认是0.75),就会触发扩容。扩容时,新容量是原来容量的1.5倍(newCapacity = oldCapacity + (oldCapacity >> 1))。然后会创建一个新的更大的数组,将旧数组中的元素复制到新数组中。例如,假设原来数组容量为10,当添加第8个元素时,就会触发扩容,新容量变为15。
  3. HashMap链表转红黑树条件
    • 当链表长度达到8时,并且此时HashMap的数组容量大于等于64,链表会转成红黑树。这是因为在哈希冲突较严重时,链表过长会导致查询性能下降,而红黑树能保证在最坏情况下,查询、插入、删除的时间复杂度为O(logn)。如果数组容量小于64,HashMap会优先选择扩容而不是转换为红黑树,因为扩容相对简单且成本较低,能在一定程度上减少哈希冲突。
  4. Spring中IOC和AOP概念
    • IOC(控制反转):传统开发中,对象的创建和管理由应用程序自身负责,这会导致对象之间的耦合度较高。IOC则将对象的创建和管理交给Spring容器,应用程序只需要使用对象,而不需要关心对象是如何创建和管理的。例如,一个Service类依赖另一个Dao类,在IOC模式下,Spring容器会创建并注入Dao对象到Service类中,Service类不需要自己去实例化Dao对象。这样降低了对象之间的耦合度,提高了代码的可维护性和可测试性。
    • AOP(面向切面编程):它是一种编程范式,将一些通用的功能(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,形成一个个切面。这些切面可以在不修改原有业务逻辑代码的情况下,切入到业务逻辑的特定位置执行。比如,在一个电商系统中,订单处理的业务逻辑可能需要记录日志,通过AOP可以将日志记录功能封装成一个切面,在订单处理方法执行前后自动记录日志,而不需要在订单处理的业务代码中添加大量的日志记录代码。
  5. Spring实现AOP方式
    • JDK动态代理:JDK动态代理是基于接口的代理方式。如果目标对象实现了接口,Spring会使用JDK动态代理创建代理对象。它通过InvocationHandler接口来处理方法调用,在代理对象调用方法时,实际会调用InvocationHandlerinvoke方法,在这个方法中可以实现切面逻辑,然后再通过反射调用目标对象的方法。例如,有一个UserService接口及其实现类UserServiceImpl,Spring会创建一个实现了UserService接口的代理类,代理类中的方法调用会被InvocationHandler处理。
    • CGLIB代理:当目标对象没有实现接口时,Spring会使用CGLIB代理。CGLIB是通过继承目标类来创建代理对象,它会生成一个目标类的子类,在子类中重写目标类的方法,在重写的方法中加入切面逻辑。例如,对于一个没有实现接口的UserDao类,CGLIB会创建一个UserDao的子类作为代理对象,在子类的方法中实现切面功能。Spring会根据目标对象是否实现接口来选择使用JDK动态代理还是CGLIB代理。
  6. Spring Boot核心特性
    • 自动配置:Spring Boot能根据项目中引入的依赖,自动配置相关的组件。例如,当项目中引入了spring - boot - starter - jdbc依赖,Spring Boot会自动配置数据源、JdbcTemplate等相关组件,开发者不需要手动编写大量的配置代码。它通过条件注解(如@Conditional)来判断是否需要配置某个组件,只有在满足一定条件时才会进行配置。
    • 起步依赖:起步依赖是一种特殊的Maven或Gradle依赖,它将一组相关的依赖聚合在一起。例如,spring - boot - starter - web起步依赖,它包含了Spring MVC、Tomcat等用于开发Web应用的必要依赖。开发者只需要引入这个起步依赖,就可以快速搭建一个Web项目,而不需要逐个引入相关的依赖,大大简化了项目的依赖管理。
  7. MyBatis中#{}和${}的区别
    • #{}:#{}是预编译方式,MyBatis在处理#{}时,会将SQL中的#{}替换为?,然后使用PreparedStatement进行参数设置。这种方式能有效防止SQL注入攻击,因为参数是作为字符串传入的,而不是直接嵌入SQL语句中。例如,SELECT * FROM user WHERE username = #{username},在执行时会将#{username}替换为?,然后通过PreparedStatement.setString(1, usernameValue)设置参数值。
    • **{}**:{}是字符串替换方式,MyBatis会直接将中的内容替换到SQL语句中。这种方式不安全,容易导致SQL注入攻击。例如,SELECTFROMuserWHEREusername={}中的内容替换到SQL语句中。这种方式不安全,容易导致SQL注入攻击。例如,`SELECT * FROM user WHERE username = {username},如果username的值被恶意修改为' OR '1' = '1'`,就会导致SQL语句被篡改,查询出所有用户数据。所以在使用${}时,要确保传入的值是安全的,一般用于传入表名、列名等不会引起SQL注入的场景。
  8. Dubbo服务调用流程
    • 服务注册:服务提供者将自己提供的服务注册到注册中心(如Zookeeper)。服务提供者在启动时,会向注册中心发送注册请求,将服务的接口、实现类、地址等信息注册到注册中心。注册中心会维护一个服务列表,记录各个服务提供者的信息。
    • 服务订阅:服务消费者启动时,会从注册中心订阅自己需要的服务。注册中心会将服务提供者的信息推送给服务消费者,包括服务的地址、端口等。服务消费者会缓存这些信息,以便后续调用。
    • 服务调用:服务消费者根据缓存的服务提供者信息,通过网络调用服务提供者的接口。Dubbo支持多种通信协议,如Dubbo协议、HTTP协议等。在调用过程中,Dubbo会进行负载均衡,从多个服务提供者中选择一个进行调用,以提高系统的可用性和性能。例如,当有多个服务提供者提供相同的服务时,Dubbo可以根据随机、轮询、权重等负载均衡策略选择一个服务提供者进行调用。
  9. RabbitMQ常见应用场景
    • 异步处理:在一些业务场景中,有些操作不需要立即得到结果,可以将这些操作异步处理。例如,在电商系统中,用户下单后,需要发送短信通知用户、更新库存等操作。这些操作可以通过发送消息到RabbitMQ队列,由专门的消费者异步处理,而不需要在下单的主流程中同步执行,这样可以提高下单的响应速度,提升用户体验。
    • 削峰填谷:在高并发场景下,系统可能会面临瞬间大量的请求,直接处理这些请求可能会导致系统崩溃。RabbitMQ可以作为一个缓冲区,将请求以消息的形式存入队列,然后系统按照自己的处理能力从队列中消费消息,逐步处理请求,避免系统因瞬间高并发而崩溃。比如,在秒杀活动中,大量用户同时请求下单,将这些下单请求发送到RabbitMQ队列,系统按照一定的速度从队列中取出请求进行处理,防止数据库等后端服务因压力过大而瘫痪。
  10. xxl - job分布式任务调度实现
  • 调度中心:xxl - job有一个调度中心,它是整个任务调度的核心。调度中心负责管理任务,包括任务的新增、修改、删除等操作。它还负责触发任务调度,按照设定的调度规则(如定时任务的cron表达式)将任务推送给执行器。调度中心通常是一个独立的服务,可以部署为集群模式,提高可用性。
  • 执行器:执行器是任务的实际执行者,它负责接收调度中心推送的任务,并执行任务逻辑。执行器可以部署在多个节点上,形成分布式执行的架构。执行器与调度中心通过网络进行通信,获取任务并汇报任务执行结果。例如,在一个电商系统中,可能有多个服务器节点部署了执行器,调度中心可以将订单统计任务推送给不同的执行器节点执行,实现分布式任务处理。
  • 任务注册与发现:执行器启动时,会向调度中心注册自己,包括执行器的名称、地址等信息。调度中心维护一个执行器列表,当需要调度任务时,根据任务配置的执行器信息,选择合适的执行器推送任务。同时,调度中心可以通过心跳检测机制,检测执行器的存活状态,对于长时间没有心跳的执行器,调度中心会将其从可用列表中移除,保证任务调度的可靠性。