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

83 阅读14分钟

第一轮面试 面试官:先从基础的 Java 核心知识问起。Java 中重载和重写的区别是什么? 王铁牛:重载是在一个类中,方法名相同但参数列表不同;重写是子类重写父类的方法,方法名、参数列表、返回值类型都要一样,而且访问修饰符不能比父类更严格。 面试官:回答得不错。那说说 ArrayList 和 LinkedList 的区别。 王铁牛:ArrayList 基于数组,随机访问快,增删慢;LinkedList 基于链表,增删快,随机访问慢。 面试官:很好。HashMap 在 JDK1.7 和 JDK1.8 中有什么主要区别? 王铁牛:1.8 中引入了红黑树,当链表长度大于 8 且数组长度大于 64 时,链表会转成红黑树,查询效率更高。1.7 是数组加链表结构。

第二轮面试 面试官:接下来聊聊多线程和 JUC 相关。讲讲线程的生命周期有哪些状态? 王铁牛:新建、就绪、运行、阻塞、死亡这几个状态。 面试官:那线程池的核心参数有哪些,分别有什么作用? 王铁牛:有核心线程数、最大线程数、存活时间、阻塞队列。核心线程数是线程池初始化的线程数,最大线程数是能容纳的最大线程数,存活时间是多余线程在空闲时的存活时长,阻塞队列用来存放等待执行的任务。 面试官:好,说说 CountDownLatch 的作用。 王铁牛:嗯……好像是用来让一个线程等待其他线程完成一组操作的。

第三轮面试 面试官:进入框架部分。Spring 中 Bean 的作用域有哪些? 王铁牛:有单例、原型、请求、会话等作用域。单例就是整个应用只有一个实例,原型每次请求都会创建新实例。 面试官:Spring Boot 自动配置的原理是什么? 王铁牛:呃……好像是通过一些配置类和条件注解,自动帮我们配置一些常用的组件。 面试官:MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译处理,{} 是字符串替换,#{} 能防止 SQL 注入。 面试官:Dubbo 有哪些核心功能? 王铁牛:好像有服务注册与发现,还有负载均衡这些。 面试官:RabbitMQ 的工作模式有哪些? 王铁牛:有简单模式、工作队列模式、发布订阅模式这些。 面试官:xxl - job 是什么,有什么特点? 王铁牛:它是一个分布式任务调度平台,特点嘛……就是能分布式调度任务。 面试官:Redis 有哪些数据类型,分别适用于什么场景? 王铁牛:有字符串、哈希、列表、集合、有序集合。字符串存简单数据,哈希存对象,列表做队列,集合去重,有序集合可以根据分数排序。

面试总结:从整体面试情况来看,你在一些基础的 Java 核心知识、集合框架方面回答得不错,展现出了一定的基础知识储备。在多线程、JUC 部分,对于基础概念掌握较好,但对于 CountDownLatch 这类工具的阐述稍显简略。在框架方面,Spring 的 Bean 作用域、MyBatis 的基础区别回答正确,但对于 Spring Boot 自动配置原理、Dubbo 核心功能等较为深入的问题,回答不够清晰全面。RabbitMQ、xxl - job、Redis 相关内容回答得中规中矩。综合考虑,我们会在接下来的几天内给你回复,你回家等通知吧。

问题答案

  1. Java 中重载和重写的区别
    • 重载(Overloading):发生在同一个类中,方法名必须相同,参数列表不同(参数个数、类型、顺序至少有一个不同),与方法的返回值类型、访问修饰符无关。例如,一个类中有 public void print(int num)public void print(String str) 就是方法重载。重载主要用于在一个类中提供多种功能相似但参数不同的方法,方便调用者根据不同的参数情况选择合适的方法。
    • 重写(Overriding):发生在子类与父类之间,子类重写父类的方法。方法名、参数列表、返回值类型(JDK1.5 后,返回值类型可以是父类方法返回值类型的子类)必须相同,访问修饰符不能比父类更严格(可以相同或更宽松)。例如,父类有 public void run() 方法,子类重写为 public void run()。重写主要用于子类根据自身需求对父类的方法进行不同的实现,体现了多态性。
  2. ArrayList 和 LinkedList 的区别
    • 数据结构:ArrayList 基于动态数组实现,内部维护一个数组,当数组容量不足时会进行扩容;LinkedList 基于双向链表实现,每个节点包含前驱节点、后继节点和数据。
    • 随机访问效率:ArrayList 支持随机访问,通过索引可以直接定位到元素,时间复杂度为 O(1);LinkedList 随机访问效率低,需要从链表头或尾开始遍历,时间复杂度为 O(n)。
    • 增删效率:在 ArrayList 中间插入或删除元素时,需要移动后续元素,效率较低,时间复杂度为 O(n);在 LinkedList 中增删元素只需修改前后节点的指针,效率较高,时间复杂度为 O(1)。但如果是在 ArrayList 尾部添加元素,时间复杂度为 O(1),因为不需要移动元素。
    • 内存占用:ArrayList 内存占用相对紧凑,因为它是数组结构;LinkedList 每个节点除了数据还需要额外存储前驱和后继节点的引用,内存占用相对较大。
  3. HashMap 在 JDK1.7 和 JDK1.8 中的主要区别
    • 数据结构:JDK1.7 中 HashMap 是数组 + 链表结构;JDK1.8 中当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树,即数组 + 链表 + 红黑树结构。
    • 哈希冲突解决方式:JDK1.7 采用头插法插入新元素到链表,在多线程环境下可能会形成环形链表导致死循环;JDK1.8 采用尾插法,避免了环形链表问题。
    • 扩容机制:JDK1.7 扩容时,需要重新计算每个元素的哈希值并重新插入到新的数组位置;JDK1.8 中扩容时,部分元素可以直接根据原索引位置或原索引位置加上旧数组长度来确定新位置,减少了重新计算哈希值的开销。
  4. 线程的生命周期有哪些状态
    • 新建(New):当创建一个 Thread 对象时,线程处于新建状态,此时线程还没有开始运行。例如 Thread thread = new Thread();
    • 就绪(Runnable):调用 start() 方法后,线程进入就绪状态,等待 CPU 调度执行。此时线程已经具备了运行的条件,但还没有分配到 CPU 资源。
    • 运行(Running):当 CPU 调度到该线程时,线程进入运行状态,执行 run() 方法中的代码。
    • 阻塞(Blocked):线程在运行过程中,遇到某些情况(如等待 I/O 操作完成、获取锁失败等),会进入阻塞状态,此时线程暂停执行,让出 CPU 资源。例如线程调用 sleep() 方法、等待获取 synchronized 锁等。
    • 死亡(Dead):线程执行完 run() 方法或者遇到未捕获的异常退出 run() 方法,线程进入死亡状态,生命周期结束。
  5. 线程池的核心参数有哪些,分别有什么作用
    • 核心线程数(corePoolSize):线程池初始化时创建的线程数量,这些线程会一直存活,即使处于空闲状态也不会被销毁(除非设置了 allowCoreThreadTimeOut 为 true)。例如,设置核心线程数为 5,线程池启动时就会创建 5 个线程。
    • 最大线程数(maximumPoolSize):线程池中允许存在的最大线程数量。当任务队列已满且核心线程都在忙碌时,线程池会创建新的线程,直到线程数量达到最大线程数。
    • 存活时间(keepAliveTime):当线程数量超过核心线程数时,多余的空闲线程在终止之前等待新任务的最长时间。例如,存活时间设置为 10 秒,一个多余的空闲线程在 10 秒内没有接到新任务,就会被销毁。
    • 时间单位(unit):存活时间的时间单位,如 TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)等。
    • 阻塞队列(workQueue):用于存放等待执行的任务。常见的阻塞队列有 ArrayBlockingQueue(有界数组队列)、LinkedBlockingQueue(无界链表队列)、SynchronousQueue(同步队列)等。当核心线程都在忙碌时,新任务会被放入阻塞队列。
  6. CountDownLatch 的作用:CountDownLatch 是 JUC 包下的一个同步工具类,它允许一个或多个线程等待其他一组线程完成操作后再继续执行。它内部维护一个计数器,通过构造函数传入初始计数值。当调用 countDown() 方法时,计数器减 1,当计数器减为 0 时,等待在 await() 方法上的线程会被唤醒继续执行。例如,在一个多线程计算任务中,主线程需要等待多个子线程完成计算后再汇总结果,就可以使用 CountDownLatch。主线程调用 await() 方法等待,子线程完成任务后调用 countDown() 方法。
  7. Spring 中 Bean 的作用域有哪些
    • 单例(Singleton):在整个 Spring 容器中,只会创建一个 Bean 实例,所有对该 Bean 的请求都会返回同一个实例。这是默认的作用域。例如,配置一个 @Service 注解的服务类,默认就是单例作用域,在整个应用中只有一个实例。
    • 原型(Prototype):每次请求获取 Bean 时,都会创建一个新的实例。适用于有状态且需要频繁创建销毁的 Bean。例如,一个处理用户请求的临时对象,每次请求都创建新的实例,避免状态干扰。
    • 请求(Request):在一次 HTTP 请求中,只会创建一个 Bean 实例,不同请求会创建不同的实例。适用于 Web 应用中与请求相关的 Bean,如处理请求的控制器。
    • 会话(Session):在一个 HTTP 会话中,只会创建一个 Bean 实例,不同会话会创建不同的实例。适用于与用户会话相关的 Bean,如保存用户会话信息的对象。
    • 全局会话(GlobalSession):在一个全局的 HTTP 会话中,只会创建一个 Bean 实例,通常用于 Portlet 应用。
  8. Spring Boot 自动配置的原理:Spring Boot 自动配置主要依赖于 @EnableAutoConfiguration 注解及其相关机制。它通过 SpringFactoriesLoader 机制,在 META - INF/spring.factories 文件中查找所有的自动配置类。这些自动配置类使用 @Configuration 注解标记为配置类,并且通过 @Conditional 系列注解(如 @ConditionalOnClass@ConditionalOnProperty 等)来判断是否满足自动配置的条件。例如,当项目中引入了 spring - data - jpa 依赖,JpaRepositoriesAutoConfiguration 类中的 @ConditionalOnClass(JpaRepository.class) 条件满足,就会自动配置 JPA 相关的 Bean,如 EntityManagerFactoryJpaTransactionManager 等,从而简化了 Spring 应用的配置。
  9. MyBatis 中 #{} 和 ${} 的区别
    • #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ?,并使用 PreparedStatementsetXXX() 方法来设置参数值,这样可以有效防止 SQL 注入。例如,select * from user where username = #{username},在执行时会将 #{username} 替换为 ?,然后通过 PreparedStatement.setString(1, usernameValue) 设置参数。
    • **:是字符串替换,MyBatis在处理{}**:是字符串替换,MyBatis 在处理 `{}时,会直接将替换为变量的值,不会进行预编译处理。例如,selectfromuserwhereusername={}` 替换为变量的值,不会进行预编译处理。例如,`select * from user where username = '{username}',如果 username的值为'admin' or '1'='1',就会导致 SQL 注入问题。一般 ${}` 用于传入数据库对象,如表名、列名等,但使用时要特别小心。
  10. Dubbo 有哪些核心功能
    • 服务注册与发现:Dubbo 提供了服务注册中心(如 Zookeeper、Nacos 等),服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,实现服务的动态发现与调用。
    • 负载均衡:当有多个服务提供者提供相同服务时,Dubbo 支持多种负载均衡策略,如随机、轮询、最少活跃调用数等,根据不同的策略将请求均匀分配到各个服务提供者上,提高系统的可用性和性能。
    • 远程调用:Dubbo 采用高性能的 RPC 通信协议(如 Dubbo 协议、HTTP 协议等),实现服务提供者和服务消费者之间的远程方法调用,就像调用本地方法一样方便。
    • 服务治理:Dubbo 提供了丰富的服务治理功能,如服务降级、服务容错、服务监控等。服务降级可以在服务出现故障或压力过大时,返回默认值或执行备用逻辑;服务容错可以通过重试、熔断等机制保证系统的稳定性;服务监控可以实时监控服务的调用情况、性能指标等。
  11. RabbitMQ 的工作模式有哪些
    • 简单模式(Simple):一个生产者,一个消费者,一条队列。生产者将消息发送到队列,消费者从队列中获取消息并处理。例如,一个简单的订单处理系统,生产者发送订单消息到队列,消费者从队列取出订单进行处理。
    • 工作队列模式(Work Queue):一个生产者,多个消费者,一条队列。多个消费者竞争从队列中获取消息,每个消息只会被一个消费者处理。适用于任务量较大,需要多个消费者共同处理的场景,如批量数据处理任务。
    • 发布订阅模式(Publish/Subscribe):一个生产者,多个消费者,交换机(Exchange)和多个队列。生产者将消息发送到交换机,交换机将消息广播到所有绑定的队列,每个队列的消费者都能收到消息。例如,一个新闻发布系统,生产者发布新闻消息到交换机,不同类型的新闻队列(如体育新闻、娱乐新闻等)的消费者都能收到消息。
    • 路由模式(Routing):一个生产者,多个消费者,交换机和多个队列。交换机根据消息的路由键(routing key)将消息发送到特定的队列。只有队列绑定的路由键与消息的路由键匹配时,队列才能收到消息。例如,在一个物流系统中,根据订单的不同地区(作为路由键)将订单消息发送到不同地区的处理队列。
    • 主题模式(Topic):类似于路由模式,但路由键支持通配符。交换机根据通配符匹配规则将消息发送到相应队列。例如,路由键为 *.news,表示匹配所有以 news 结尾的路由键,这样可以更灵活地进行消息分发。
  12. xxl - job 是什么,有什么特点:xxl - job 是一个轻量级分布式任务调度平台,具有以下特点:
    • 分布式调度:支持分布式部署,能够在多个节点上调度任务,提高任务处理能力和可靠性。
    • 可视化管理:提供了简洁易用的 Web 控制台,方便用户进行任务的创建、修改、删除、暂停、恢复等操作,以及查看任务执行日志、执行结果等。
    • 动态任务编排:支持通过图形化界面进行任务依赖关系的配置,实现任务的顺序执行、并行执行等复杂的任务编排。
    • 多种触发方式:支持定时触发、手动触发、API 触发等多种任务触发方式,满足不同业务场景的需求。
    • 容错处理:具备任务失败重试机制,可配置重试次数和重试间隔,保证任务执行的可靠性。同时支持任务阻塞处理策略,如单机串行、丢弃后续调度、覆盖之前调度等。
    • 支持多种语言:任务执行器支持多种语言开发,不限于 Java,方便与不同技术栈的项目集成。
  13. Redis 有哪些数据类型,分别适用于什么场景
    • 字符串(String):最基本的数据类型,适用于存储简单的键值对,如缓存用户信息、配置参数等。例如,存储用户的登录状态 set user:1:status loggedIn
    • 哈希(Hash):用于存储对象,以字段和值的形式存储。适用于存储一个对象的多个属性,如用户的详细信息(姓名、年龄、地址等),可以使用 hset user:1 name "John" age 30 address "New York" 存储,获取时可以使用 hgetall user:1 获取所有属性。
    • 列表(List):基于双向链表实现,可以用作队列或栈。用作队列时,从列表左侧插入元素,从右侧取出元素(rpushlpop);用作栈时,从列表左侧插入和