《互联网大厂 Java 面试:核心知识、框架与中间件的全方位考验》

49 阅读12分钟

互联网大厂 Java 面试:核心知识、框架与中间件的全方位考验

一位严肃的面试官坐在桌前,对面是略显紧张的求职者王铁牛,一场关于 Java 技术的面试即将拉开帷幕。

第一轮面试开始 面试官:首先问你几个 Java 基础问题。Java 中多态的实现方式有哪些? 王铁牛:多态的实现方式主要有方法重载和方法重写。方法重载是在一个类中,有多个方法名相同,但参数列表不同;方法重写是子类重写父类的方法。 面试官:回答得不错。那说说 HashMap 的底层数据结构是什么? 王铁牛:HashMap 的底层是数组 + 链表 + 红黑树。当链表长度超过 8 且数组长度大于 64 时,链表会转化为红黑树。 面试官:很好。再问你,ArrayList 和 LinkedList 的区别是什么? 王铁牛:ArrayList 底层是动态数组,随机访问快,插入和删除慢;LinkedList 底层是双向链表,插入和删除快,随机访问慢。 面试官:非常棒,基础掌握得很扎实。

第二轮面试开始 面试官:接下来聊聊 JUC 和多线程。线程池有哪些创建方式? 王铁牛:可以通过 Executors 工具类创建,像 newFixedThreadPool、newCachedThreadPool 等,也可以通过 ThreadPoolExecutor 手动创建。 面试官:那 ThreadPoolExecutor 构造函数的参数有哪些,分别代表什么含义? 王铁牛:有 corePoolSize 核心线程数,maximumPoolSize 最大线程数,keepAliveTime 线程空闲存活时间,unit 时间单位,workQueue 任务队列,threadFactory 线程工厂,handler 任务拒绝策略。 面试官:不错。说说 JVM 的内存模型,它分为哪几个部分? 王铁牛:JVM 内存模型分为堆、栈、方法区、本地方法栈、程序计数器。堆是存放对象实例的地方,栈存放局部变量等。 面试官:回答得很清晰,看来对多线程和 JVM 有一定的理解。

第三轮面试开始 面试官:现在来谈谈框架相关的。Spring 的 IOC 和 AOP 是什么,能简单解释一下吗? 王铁牛:IOC 是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器;AOP 是面向切面编程,能在不修改原有代码的基础上增加额外功能。 面试官:那 Spring Boot 是如何实现自动配置的? 王铁牛:呃……这个……好像是通过一些配置文件和注解吧,具体的我有点不太清楚。 面试官:再问你,MyBatis 的一级缓存和二级缓存有什么区别? 王铁牛:一级缓存和二级缓存……好像一个是局部的,一个是全局的,具体细节我记不太准了。 面试官:Dubbo 是做什么的,它的工作原理是什么? 王铁牛:Dubbo 是分布式服务框架,原理嘛……就是服务注册和发现之类的,具体的不太好说。

面试总结:面试官看着王铁牛,严肃地说:“从前面的回答来看,你对 Java 基础、JUC、JVM 等方面的知识掌握得还不错,回答问题比较准确清晰,说明你在这些基础内容上下了不少功夫。然而在框架和中间件的部分,对于一些深入的问题,比如 Spring Boot 的自动配置、MyBatis 缓存的详细区别以及 Dubbo 的工作原理等,你回答得不够清晰准确,这反映出你在这些高级技术的理解和掌握上还有所欠缺。我们公司对于技术人员在框架和中间件方面的要求比较高,需要有深入的理解和实践经验。后续我们会综合评估你的整体表现,你先回家等通知吧。”

问题答案

  1. Java 中多态的实现方式有哪些?
    • 方法重载:在同一个类中,方法名相同,但参数列表不同(参数的类型、个数、顺序不同)。编译器根据调用方法时传入的参数来决定调用哪个方法。例如:
public class OverloadExample {
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }
}
- **方法重写**:子类重写父类的方法,要求方法名、参数列表和返回值类型都相同(返回值类型可以是父类返回值类型的子类,称为协变返回类型)。运行时根据对象的实际类型来决定调用哪个方法。例如:
class Animal {
    public void makeSound() {
        System.out.println("Animal makes sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}
  1. HashMap 的底层数据结构是什么?
    • HashMap 的底层是数组 + 链表 + 红黑树。数组被称为哈希桶,每个桶存储一个链表或红黑树的头节点。当调用 put 方法插入键值对时,首先根据键的哈希值计算出在数组中的位置。如果该位置为空,直接插入新节点;如果该位置已经有节点,就遍历链表或红黑树,若找到相同的键则更新值,若未找到则插入新节点。当链表长度超过 8 且数组长度大于 64 时,链表会转化为红黑树,以提高查找效率;当红黑树节点数小于 6 时,会退化为链表。
  2. ArrayList 和 LinkedList 的区别是什么?
    • 数据结构:ArrayList 底层是动态数组,数组是连续的内存空间;LinkedList 底层是双向链表,每个节点包含数据、指向前一个节点的引用和指向后一个节点的引用。
    • 随机访问:ArrayList 支持高效的随机访问,通过索引可以直接访问元素,时间复杂度为 O(1);LinkedList 随机访问效率低,需要从头或尾开始遍历链表,时间复杂度为 O(n)。
    • 插入和删除:ArrayList 在中间或开头插入、删除元素时,需要移动后续元素,时间复杂度为 O(n);LinkedList 在插入和删除元素时,只需要修改相邻节点的引用,时间复杂度为 O(1),但如果要插入或删除指定位置的元素,需要先定位到该位置,时间复杂度为 O(n)。
  3. 线程池有哪些创建方式?
    • 通过 Executors 工具类创建
      • newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程则执行任务,否则任务会被放入队列等待。
      • newCachedThreadPool:创建一个可缓存的线程池,线程数量不固定,当有新任务提交时,如果线程池中有空闲线程则执行任务,否则会创建新线程。如果线程空闲时间超过 60 秒,会被回收。
      • newSingleThreadExecutor:创建一个单线程的线程池,只有一个线程执行任务,保证任务按顺序执行。
      • newScheduledThreadPool:创建一个可定时执行任务的线程池。
    • 通过 ThreadPoolExecutor 手动创建:可以根据具体需求设置线程池的各种参数,如核心线程数、最大线程数、线程空闲存活时间等。
  4. ThreadPoolExecutor 构造函数的参数有哪些,分别代表什么含义?
    • corePoolSize:核心线程数,线程池初始化时创建的线程数量,当有新任务提交时,会优先使用核心线程执行任务。
    • maximumPoolSize:最大线程数,线程池允许的最大线程数量。当核心线程都在执行任务,且任务队列已满时,会创建新线程,直到达到最大线程数。
    • keepAliveTime:线程空闲存活时间,当线程空闲时间超过该时间,且线程数量大于核心线程数时,多余的线程会被回收。
    • unit:时间单位,如 TimeUnit.SECONDS 表示秒。
    • workQueue:任务队列,用于存储等待执行的任务。常见的任务队列有 ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)、SynchronousQueue(同步队列)等。
    • threadFactory:线程工厂,用于创建线程。可以自定义线程工厂来设置线程的名称、优先级等属性。
    • handler:任务拒绝策略,当线程池的线程数量达到最大线程数,且任务队列已满时,新提交的任务会被拒绝,此时会调用任务拒绝策略来处理。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  5. JVM 的内存模型,它分为哪几个部分?
    • 堆(Heap):是 JVM 中最大的一块内存区域,用于存储对象实例和数组。堆是所有线程共享的,垃圾回收主要针对堆进行。堆可以分为新生代和老年代,新生代又分为 Eden 区、Survivor 区(有两个 Survivor 区,分别为 From 和 To)。
    • 栈(Stack):每个线程都有自己的栈,用于存储局部变量、方法调用信息等。栈中的数据是线程私有的,每个方法在执行时会创建一个栈帧,栈帧中包含局部变量表、操作数栈、动态链接、方法出口等信息。当方法执行完毕,栈帧会被弹出。
    • 方法区(Method Area):用于存储类的元数据,如类的信息、常量池、静态变量等。方法区是所有线程共享的,在 JDK 1.8 之前,方法区也被称为永久代,JDK 1.8 及以后,使用元空间(Metaspace)来替代永久代。
    • 本地方法栈(Native Method Stack):与栈类似,但它是为本地方法(使用 native 关键字修饰的方法)服务的。
    • 程序计数器(Program Counter Register):可以看作是当前线程所执行的字节码的行号指示器。每个线程都有自己独立的程序计数器,它是线程私有的。
  6. Spring 的 IOC 和 AOP 是什么,能简单解释一下吗?
    • IOC(控制反转):传统的程序中,对象的创建和依赖关系的管理由程序本身负责,而在 Spring 中,把对象的创建和依赖关系的管理交给 Spring 容器。Spring 容器通过读取配置文件或注解,创建对象并注入依赖。例如,一个 UserService 类依赖于 UserDao 类,在传统方式中,UserService 类需要自己创建 UserDao 对象,而在 Spring 中,UserService 类只需要声明对 UserDao 的依赖,Spring 容器会自动创建 UserDao 对象并注入到 UserService 中。
    • AOP(面向切面编程):是一种编程范式,它允许在不修改原有代码的基础上,增加额外的功能。AOP 把一些通用的功能(如日志记录、事务管理等)封装成切面,在程序运行时,通过代理机制将切面织入到目标对象的方法中。例如,在方法执行前后添加日志记录,在方法执行过程中进行事务管理等。
  7. Spring Boot 是如何实现自动配置的?
    • Spring Boot 的自动配置是基于条件注解和类路径扫描实现的。当 Spring Boot 应用启动时,会自动扫描类路径下的 META - INF/spring.factories 文件,该文件中定义了一系列的自动配置类。每个自动配置类都使用了 @Configuration 注解,并且通过 @Conditional 系列注解来判断是否满足自动配置的条件。例如,@ConditionalOnClass 表示只有当类路径下存在指定的类时,才会进行自动配置;@ConditionalOnMissingBean 表示只有当容器中不存在指定类型的 Bean 时,才会创建该 Bean。Spring Boot 还会根据 application.propertiesapplication.yml 中的配置信息,对自动配置进行定制。
  8. MyBatis 的一级缓存和二级缓存有什么区别?
    • 一级缓存:是 SqlSession 级别的缓存,同一个 SqlSession 中执行相同的查询语句时,会先从一级缓存中查找,如果缓存中存在则直接返回结果,否则从数据库中查询,并将结果存入一级缓存。当 SqlSession 关闭或执行了增删改操作时,一级缓存会被清空。一级缓存是默认开启的。
    • 二级缓存:是 mapper 级别的缓存,多个 SqlSession 可以共享同一个 mapper 的二级缓存。二级缓存需要手动开启,在 mapper.xml 中添加 <cache/> 标签或在 mapper 接口上添加 @CacheNamespace 注解。当执行增删改操作时,会清空该 mapper 的二级缓存。二级缓存的作用范围更广,可以提高不同 SqlSession 之间的查询效率。
  9. Dubbo 是做什么的,它的工作原理是什么?
    • 作用:Dubbo 是一个高性能的分布式服务框架,用于解决分布式系统中服务之间的调用问题。它提供了服务注册与发现、远程调用、负载均衡、集群容错等功能,使得服务之间可以方便地进行通信和协作。
    • 工作原理
      • 服务注册:服务提供者在启动时,将自己提供的服务信息(如服务接口、服务地址等)注册到服务注册中心(如 Zookeeper)。
      • 服务发现:服务消费者在启动时,从服务注册中心获取服务提供者的信息,并缓存到本地。
      • 远程调用:服务消费者通过代理对象调用服务提供者的服务,代理对象将请求信息封装成网络数据包,通过网络发送到服务提供者。
      • 负载均衡:当有多个服务提供者时,Dubbo 会根据负载均衡策略(如随机、轮询等)选择一个服务提供者进行调用。
      • 集群容错:当服务调用失败时,Dubbo 会根据集群容错策略(如失败重试、快速失败等)进行处理。