《探秘互联网大厂Java岗面试:核心知识到热门框架全考察》

26 阅读5分钟

在互联网大厂的一间明亮会议室里,一场Java岗位的面试正在紧张进行着。面试官神情严肃,经验丰富,准备对前来应聘的求职者进行全方位的考察。而坐在对面的求职者王铁牛,心里既怀揣着期待又有些忐忑,他的技术水平嘛,只能说对一些简单知识还算熟悉,遇到复杂些的问题就有点露怯了。

第一轮面试:

面试官:(表情严肃,目光直视)先从基础的开始吧,Java核心知识里,说说Java的基本数据类型有哪些?

王铁牛:(稍微松了口气,快速回答)嗯,有byte、short、int、long、float、double、char还有boolean这几种基本数据类型。

面试官:(微微点头)不错,那接着,在多线程里,创建线程有几种方式呢?

王铁牛:(思考了一下)大概有两种吧,一种是继承Thread类,重写它的run方法;另一种是实现Runnable接口,然后把这个实现类的对象作为参数传给Thread类的构造函数来创建线程。

面试官:(继续提问)那好,再说说ArrayList和HashMap的区别吧,从底层数据结构和使用场景方面来讲。

王铁牛:(有点紧张,但还是努力回答)ArrayList底层是数组,它查询快,增删慢,适合随机访问元素的场景。HashMap底层是哈希表,存储的是键值对,查找、插入、删除都比较快,适合根据键快速查找值的情况。

面试官:(嘴角微微上扬)嗯,这几个问题回答得还可以。

第二轮面试:

面试官:(语气加重了些)接下来问点深入点的。在JUC里,讲讲CountDownLatch的作用和使用场景吧。

王铁牛:(心里一紧,开始瞎编)呃,这个CountDownLatch好像是用来控制线程的执行顺序的吧,就是让一些线程等着,等其他线程都完成了某个任务后,再接着执行。具体场景嘛,我也不太清楚,大概就是那种有先后顺序要求的任务吧。(说完擦了擦额头的汗)

面试官:(皱了下眉头)那行,再说说JVM里,垃圾回收算法有哪些?简单说下它们的原理。

王铁牛:(更加慌乱)嗯,有那个标记清除算法,就是先标记要回收的对象,然后再清除掉。还有那个复制算法,好像是把内存分成两块,只用一块,用完了就把存活的对象复制到另一块去。(说得含糊不清)

面试官:(脸色不太好看)那在Spring框架里,说说依赖注入有几种方式?

王铁牛:(硬着头皮回答)好像有两种吧,一种是通过构造函数注入,还有一种是通过setter方法注入,具体我也不是特别清楚了。(声音越来越小)

第三轮面试:

面试官:(严肃依旧)再看看其他方面的。在SpringBoot里,它的自动配置原理你能讲讲吗?

王铁牛:(完全懵了,乱说一通)呃,就是它能自动帮我们配置好很多东西,好像是根据一些默认的配置文件吧,具体怎么实现的我不太明白。

面试官:(无奈地摇摇头)那MyBatis里,#{}和${}的区别你知道吗?

王铁牛:(胡乱猜测)嗯,好像一个是用来传参数的,一个是用来拼接SQL语句的吧,具体我也不太确定。

面试官:(又问)还有Dubbo框架,简单说下它的核心功能和应用场景。

王铁牛:(脑子一片混乱)Dubbo啊,我就知道它是用来做分布式的,具体核心功能和场景我真不太懂了。

面试官:(深吸一口气,靠在椅背上)好了,今天的面试就先到这儿吧,你先回去等通知吧,我们后续会根据整体情况再做评估的。

王铁牛:(垂头丧气地站起来)好的,谢谢面试官,希望能有好消息。(说完便灰溜溜地走了)

面试官看着王铁牛离去的背影,无奈地摇了摇头,心里想着:这求职者基础还行,但一遇到稍微复杂点的问题就露馅了,看来还得继续筛选更合适的人才啊。

以下是上述问题的详细答案:

第一轮面试答案:

  • Java的基本数据类型

    • Java的基本数据类型分为四类八种。
    • 整数类型:byte(占1个字节,范围是 -128到127)、short(占2个字节,范围是 -32768到32767)、int(占4个字节,范围是 -2147483648到2147483647)、long(占8个字节,范围很大,用于表示较大的整数,在赋值时需要在数字后面加L或l,比如100L)。
    • 浮点类型:float(占4个字节,单精度浮点数,在赋值时需要在数字后面加F或f,比如3.14F)、double(占8个字节,双精度浮点数,是Java中默认的浮点数类型,如3.14)。
    • 字符类型:char(占2个字节,用于表示单个字符,比如 'A'、'中' 等)。
    • 布尔类型:boolean(占1个字节,只有true和false两个值,用于逻辑判断)。
  • 创建线程的方式

    • 继承Thread类:通过创建一个类继承Thread类,然后重写run方法,在run方法中编写线程要执行的任务逻辑。例如:
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行的任务");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
- 实现Runnable接口:创建一个类实现Runnable接口,实现其run方法,然后将该实现类的对象作为参数传递给Thread类的构造函数来创建线程。例如:
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程执行的任务");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}
  • ArrayList和HashMap的区别
    • 底层数据结构
      • ArrayList底层是基于数组实现的动态数组。它在内存中是一段连续的存储空间,通过索引可以快速访问数组中的元素,时间复杂度为O(1)。但在进行插入和删除操作时(除了在末尾插入和删除),需要移动后面的元素,时间复杂度为O(n)。
      • HashMap底层是哈希表(数组 + 链表/红黑树)实现的。它通过对键进行哈希运算得到一个哈希值,然后根据这个哈希值确定元素在数组中的存储位置。当发生哈希冲突(不同的键计算出相同的哈希值)时,会在该位置形成链表(当链表长度超过一定阈值时会转换为红黑树来提高性能)。它的查找、插入、删除操作的平均时间复杂度接近O(1),但在最坏情况下可能达到O(n)。
    • 使用场景
      • ArrayList适合于需要频繁访问元素,且元素数量相对固定或者增长不太频繁的场景。比如存储一组学生的成绩,需要经常根据索引查询某个学生的成绩。
      • HashMap适合于需要根据键快速查找对应的值的场景。比如存储用户信息,以用户ID为键,用户详细信息为值,通过用户ID可以快速获取到对应的用户信息。

第二轮面试答案:

  • CountDownLatch的作用和使用场景
    • 作用:CountDownLatch是一个同步辅助类,它允许一个或多个线程等待其他线程完成一组操作后再继续执行。它内部维护了一个计数器,这个计数器的初始值是在创建CountDownLatch时指定的。当其他线程完成了各自的任务后,会调用countDown方法来递减这个计数器,当计数器的值减为0时,等待在这个CountDownLatch上的线程就会被唤醒并继续执行。
    • 使用场景:比如在多线程环境下,主线程需要等待多个子线程完成某项任务后再进行后续的操作。例如,有一个任务是统计多个文件中的数据总和,我们可以为每个文件创建一个子线程去统计该文件的数据,然后主线程使用CountDownLatch等待所有子线程完成统计后,再将各个子线程统计的结果汇总起来。示例代码如下:
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int numThreads = 5;
        CountDownLatch latch = new CountDownLatch(numThreads);

        for (int i = 0; i < numThreads; i++) {
            new Thread(() -> {
                // 模拟子线程执行任务
                System.out.println(Thread.currentThread().getName() + " is working");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }).start();
        }

        latch.wait();
        System.out.println("All threads have finished their work");
    }
}
  • JVM里的垃圾回收算法

    • 标记清除算法
      • 原理:该算法分为“标记”和“清除”两个阶段。首先从根对象(如栈帧中的局部变量、静态变量等)开始,通过可达性分析算法标记出所有从根对象可达的对象,这些对象被认为是存活的;然后遍历整个堆内存,清除掉那些没有被标记的对象,也就是认为是垃圾的对象。
      • 优缺点:优点是实现简单,不需要额外的内存空间来进行对象的移动等操作。缺点是会产生内存碎片,当需要分配较大的连续内存空间时,可能会因为内存碎片而无法满足需求,导致内存分配失败。
    • 复制算法
      • 原理:将可用内存划分为大小相等的两块,每次只使用其中一块。当这块内存使用完后,将存活的对象复制到另一块空闲的内存中,然后把原来使用的那块内存全部清空,下次就可以继续使用这块被清空的内存了。
      • 优缺点:优点是不会产生内存碎片,因为每次都是复制存活的对象到新的内存区域,新的内存区域是连续的。缺点是需要额外的内存空间来进行复制操作,而且如果存活的对象较多,复制的成本就会比较高,一般适用于对象存活率较低的场景,比如新生代的垃圾回收。
  • Spring框架里依赖注入的方式

    • 构造函数注入:通过在类的构造函数中传入需要依赖的对象,在创建类的实例时,由Spring容器自动将依赖的对象注入进来。例如:
public class MyService {
    private MyRepository myRepository;

    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}
- **setter方法注入**:在类中定义setter方法来设置需要依赖的对象,Spring容器会在创建类的实例后,通过调用setter方法将依赖的对象注入进来。例如:
public class MyService {
    private MyRepository myRepository;

    public void setMyRepository(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

第三轮面试答案:

  • SpringBoot的自动配置原理

    • SpringBoot的自动配置是基于条件注解和自动配置类来实现的。
    • 首先,SpringBoot在启动时会扫描类路径下的所有META-INF/spring.factories文件,这些文件中定义了一系列的自动配置类。
    • 每个自动配置类上通常会标注有条件注解,比如@ConditionalOnClass、@ConditionalOnMissingClass、@ConditionalOnProperty等。这些条件注解会根据当前项目的环境、是否存在某些类、是否满足某些属性条件等来判断该自动配置类是否应该生效。
    • 当满足所有条件时,自动配置类中的配置方法就会被执行,这些配置方法会向Spring容器中注入各种组件、配置Bean等,从而实现了自动配置项目中常见的功能,比如配置数据源、配置Web相关组件等,使得开发者不需要手动去配置大量的基础设置,大大提高了开发效率。
  • MyBatis里#{}和${}的区别

    • #{}
      • #{}是预编译处理的占位符,在SQL语句执行前,MyBatis会将SQL语句中的#{}替换为实际的参数值,并进行预编译处理。这样做的好处是可以防止SQL注入攻击,因为它会将参数值作为一个整体进行处理,不会将参数值直接拼接在SQL语句中。例如:
SELECT * FROM users WHERE username = #{username}
    - 当传入参数username的值为'admin'时,MyBatis实际执行的SQL语句可能是:
SELECT * FROM users WHERE username = 'admin'
- **${}**    - ${}是字符串拼接的占位符,它会将传入的参数值直接拼接在SQL语句中。这种方式在某些特定场景下可能会用到,比如动态指定表名、列名等,但使用时要特别小心,因为如果参数值是由用户输入的,就很容易导致SQL注入攻击。例如:
SELECT * FROM ${tableName} WHERE condition = 'value'
    - 当传入参数tableName的值为'users'时,MyBatis实际执行的SQL语句就是:
SELECT * FROM users WHERE condition = 'value'
  • Dubbo框架的核心功能和应用场景
    • 核心功能
      • 远程调用:Dubbo提供了高效的远程调用机制,使得不同服务之间可以方便地进行通信和调用。它支持多种协议,如Dubbo协议、HTTP协议等,通过这些协议可以实现服务提供者和服务消费者之间的远程交互。
      • 服务注册与发现:Dubbo有一套完善的服务注册与发现机制。服务提供者在启动时会将自己提供的服务信息注册到注册中心(如Zookeeper、Nacos等),服务消费者在需要调用服务时,会从注册中心获取服务提供者的信息,然后根据这些信息进行远程调用。
      • 负载均衡:当有多个服务提供者提供相同的服务时,Dubbo会根据一定的负载均衡策略(如随机、轮询、加权轮询等)将服务消费者的请求分配到不同的服务提供者上,以实现资源的合理利用和提高系统的整体性能。
      • 集群容错:Dubbo提供了多种集群容错策略,如失败重试、快速失败、故障转移等。当服务提供者出现故障时,服务消费者可以根据设定的集群容错策略采取相应的措施,保证系统的正常运行。
    • 应用场景
      • 分布式系统:Dubbo是构建分布式系统的常用框架之一,在大型互联网项目中,将不同的业务功能拆分成多个独立的服务,通过Dubbo实现这些服务之间的远程调用、服务注册与发现等功能,从而提高系统的可扩展性、可维护性和性能。
      • 微服务架构:在微服务架构下,Dubbo可以很好地满足各个微服务之间的通信需求,实现微服务之间的高效协作,是微服务开发中重要的技术支撑。