目 录CONTENT

文章目录

海尔经典面试题

陌念
2025-03-25 / 0 评论 / 2 点赞 / 62 阅读 / 0 字
温馨提示:
本文最后更新于2025-05-16,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

以下文章来源于微信公众号:小林coding 严禁加班,强制下班,真的发生了。。。

https://xiaolincoding.com

海尔是以家电制造为主,Java 软开的岗位肯定是没有互联网公司多的,我也是找了很久才找到海尔的Java 软开面经,而且还是几年前的,不过面试的问题还是蛮经典,还是值得学习一波。

海尔(一面通过) 

Redis 有遇到过缓存击穿和缓存雪崩的情况吗?怎么解决的?

  • 缓存雪崩:当大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

  • 缓存击穿:如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

  • 缓存穿透:当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

缓存雪崩解决方案:

  • 均匀设置过期时间:如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。

  • 互斥锁:当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。实现互斥锁的时候,最好设置超时时间,不然第一个请求拿到了锁,然后这个请求发生了某种意外而一直阻塞,一直不释放锁,这时其他请求也一直拿不到锁,整个系统就会出现无响应的现象。

  • 后台更新缓存:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新

缓存击穿解决方案:

  • 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。

  • 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

缓存穿透解决方案:

  • 非法请求的限制:当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。

  • 缓存空值或者默认值:当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。

  • 布隆过滤器:我们可以在写入数据库数据时,使用布隆过滤器做个标记,然后在用户请求到来时,业务线程确认缓存失效后,可以通过查询布隆过滤器快速判断数据是否存在,如果不存在,就不用通过查询数据库来判断数据是否存在。即使发生了缓存穿透,大量请求只会查询 Redis 和布隆过滤器,而不会查询数据库,保证了数据库能正常运行,Redis 自身也是支持布隆过滤器的。

讲讲 Spring Boot 加载初始化的过程?

SpringBoot是一个服务Spring框架的框架,能够简化配置文件,快速构建web应用,内置tomcat,无需打包部署,直接运行。 当我们启动一个SpringBoot应用的时候,都会用到如下的启动类:

@SpringBootApplication
public class Application {
     public static void main(String[] args) {
         SpringApplication.run(Application.class, args);
     }
}

只要加上@SpringBootApplication,然后执行run()方法,就可以启动一个应用程序,启动的流程如下:

  1. 首先从main找到run()方法,在执行run()方法之前new一个SpringApplication对象

  2. 进入run()方法,创建应用监听器SpringApplicationRunListeners开始监听

  3. 然后加载SpringBoot配置环境(ConfigurableEnvironment),然后把配置环境(Environment)加入监听对象中

  4. 然后加载应用上下文(ConfigurableApplicationContext),当做run方法的返回对象

  5. 最后创建Spring容器,refreshContext(context),实现starter自动化配置和bean的实例化等工作。

平时常用的注解有哪些?

Bean 相关:

  • @Component:将一个类标识为 Spring 组件(Bean),可以被 Spring 容器自动检测和注册。通用注解,适用于任何层次的组件。

  • @ComponentScan:自动扫描指定包及其子包中的 Spring 组件。

  • @Controller:标识控制层组件,实际上是 @Component 的一个特化,用于表示 Web 控制器。处理 HTTP 请求并返回视图或响应数据。

  • @RestController:是 @Controller 和 @ResponseBody 的结合,返回的对象会自动序列化为 JSON 或 XML,并写入 HTTP 响应体中。

  • @Repository:标识持久层组件(DAO 层),实际上是 @Component 的一个特化,用于表示数据访问组件。常用于与数据库交互。

  • @Bean:方法注解,用于修饰方法,主要功能是将修饰方法的返回对象添加到 Spring 容器中,使得其他组件可以通过依赖注入的方式使用这个对象。

依赖注入:

  • @Autowired:用于自动注入依赖对象,Spring 框架提供的注解。

  • @Resource:按名称自动注入依赖对象(也可以按类型,但默认按名称),JDK 提供注解。

  • @Qualifier:与 @Autowired 一起使用,用于指定要注入的 Bean 的名称。当存在多个相同类型的 Bean 时,可以使用 @Qualifier 来指定注入哪一个。

读取配置:

  • @Value:用于注入属性值,通常从配置文件中获取。标注在字段上,并指定属性值的来源(如配置文件中的某个属性)。

  • @ConfigurationProperties:用于将配置属性绑定到一个实体类上。通常用于从配置文件中读取属性值并绑定到类的字段上。

Web相关:

  • @RequestMapping:用于映射 HTTP 请求到处理方法上,支持 GET、POST、PUT、DELETE 等请求方法。可以标注在类或方法上。标注在类上时,表示类中的所有响应请求的方法都是以该类路径为父路径。

  • @GetMapping、@PostMapping、@PutMapping、@DeleteMapping:分别用于映射 HTTP GET、POST、PUT、DELETE 请求到处理方法上。它们是 @RequestMapping 的特化,分别对应不同的 HTTP 请求方法。

其他常用注解:

  • @Transactional:声明事务管理。标注在类或方法上,指定事务的传播行为、隔离级别等。

  • @Scheduled:声明一个方法需要定时执行。标注在方法上,并指定定时执行的规则(如每隔一定时间执行一次)。

MyBatis 有用过吗?说说 MyBatis 有什么特点?

用过,项目中有用到。MyBatis 的特点如下:

  • 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任 何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。

  • 与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不 需要手动开关连接;

  • 很好的与各种数据库兼容,因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持。

  • 能够与 Spring 很好的集成,开发效率高

  • 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射 标签,支持对象关系组件维护。

Git 使用熟练吗?

熟悉,项目的代码是用 git 工具来管理的/

git 的工作原理如下图:

几个专用名词的译名如下:

  • Workspace:工作区

  • Index / Stage:暂存区

  • Repository:仓库区(或本地仓库)

  • Remote:远程仓库

工作流程

最基础的工作流程,首先执行 git pull 获取远程仓库的最新代码,进行代码的编写。

完成相应功能的开发后执行 git add . 将工作区代码的修改添加到暂存区,再执行 git commit -m 完成xx功能 将暂存区代码提交到本地仓库并添加相应的注释,最后执行 git push 命令推送到远程仓库。

撤回 git commit 操作

当执行了 git commit -m 注释内容 命令想要撤回,可以使用 git reset --soft HEAD^ 把本地仓库回退到当前版本的上一个版本,也就是刚刚还没提交的时候,代码的改动会保留在暂存区和工作区。

也可以使用 git reset --mixed HEAD^,这样不止回退了刚刚的 git commit 操作,还回退了 git add 操作,代码的改动只会保留在工作区。因为 --mixed 参数是 git reset 命令的默认选项,也就是可以写为 git reset HEAD^

撤回 git push 操作

当执行了 git push 命令想要撤回,可以使用 git reset HEAD^ 将本地仓库回退到当前版本的上一个版本,代码的修改会保留在工作区,然后使用 git push origin xxx --force 将本地仓库当前版本的代码强制推送到远程仓库。

说说 Java 中的异常有几大类?

Java异常类层次结构图:

Java的异常体系主要基于两大类:Throwable类及其子类。Throwable有两个重要的子类:Error和Exception,它们分别代表了不同类型的异常情况。

  • Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。例如,OutOfMemoryError、StackOverflowError等。

  • Exception(异常):表示程序本身可以处理的异常条件。异常分为两大类:

    • 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出。它们通常是外部错误,如文件不存在(FileNotFoundException)、类未找到(ClassNotFoundException)等。非运行时异常强制程序员处理这些可能出现的问题,增强了程序的健壮性。

    • 运行时异常:这类异常包括运行时异常(RuntimeException)和错误(Error)。运行时异常由程序错误导致,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。运行时异常是不需要在编译时强制捕获或声明的。

Java 8 你知道有什么新特性?

下面是 Java 8 主要新特性的整理表格,包含关键改进和示例说明

特性名称 描述 示例或说明
Lambda 表达式 简化匿名内部类,支持函数式编程 (a, b) -> a + b 代替匿名类实现接口
函数式接口 仅含一个抽象方法的接口,可用 @FunctionalInterface 注解标记 RunnableComparator, 或自定义接口 @FunctionalInterface interface MyFunc { void run(); }
Stream API 提供链式操作处理集合数据,支持并行处理 list.stream().filter(x -> x > 0).collect(Collectors.toList())
Optional 类 封装可能为 null 的对象,减少空指针异常 Optional.ofNullable(value).orElse("default")
方法引用 简化 Lambda 表达式,直接引用现有方法 System.out::println等价于 x -> System.out.println(x)
接口的默认方法与静态方法 接口可定义默认实现和静态方法,增强扩展性 interface A { default void print() { System.out.println("默认方法"); } }
并行数组排序 使用多线程加速数组排序 Arrays.parallelSort(array)
重复注解 允许同一位置多次使用相同注解 @Repeatable注解配合容器注解使用
类型注解 注解可应用于更多位置(如泛型、异常等) List<@NonNull String> list
CompletableFuture 增强异步编程能力,支持链式调用和组合操作 CompletableFuture.supplyAsync(() -> "result").thenAccept(System.out::println)

Lambda 表达式了解吗?

Lambda 表达式它是一种简洁的语法,用于创建匿名函数,主要用于简化函数式接口(只有一个抽象方法的接口)的使用。其基本语法有以下两种形式:

  • (parameters) -> expression:当 Lambda 体只有一个表达式时使用,表达式的结果会作为返回值。

  • (parameters) -> { statements; }:当 Lambda 体包含多条语句时,需要使用大括号将语句括起来,若有返回值则需要使用 return 语句。

传统的匿名内部类实现方式代码较为冗长,而 Lambda 表达式可以用更简洁的语法实现相同的功能。比如,使用匿名内部类实现 Runnable 接口

public class AnonymousClassExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Running using anonymous class");
            }
        });
        t1.start();
    }
}

使用 Lambda 表达式实现相同功能:

public class LambdaExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> System.out.println("Running using lambda expression"));
        t1.start();
    }
}

可以看到,Lambda 表达式的代码更加简洁明了。

还有,Lambda 表达式能够更清晰地表达代码的意图,尤其是在处理集合操作时,如过滤、映射等。比如,过滤出列表中所有偶数

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ReadabilityExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        // 使用 Lambda 表达式结合 Stream API 过滤偶数
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());
        System.out.println(evenNumbers);
    }
}

通过 Lambda 表达式,代码的逻辑更加直观,易于理解。

还有,Lambda 表达式使得 Java 支持函数式编程范式,允许将函数作为参数传递,从而可以编写更灵活、可复用的代码。比如定义一个通用的计算函数。

interface Calculator {
    int calculate(int a, int b);
}

publicclass FunctionalProgrammingExample {
    public static int operate(int a, int b, Calculator calculator) {
        return calculator.calculate(a, b);
    }

    public static void main(String[] args) {
        // 使用 Lambda 表达式传递加法函数
        int sum = operate(3, 5, (x, y) -> x + y);
        System.out.println("Sum: " + sum);

        // 使用 Lambda 表达式传递乘法函数
        int product = operate(3, 5, (x, y) -> x * y);
        System.out.println("Product: " + product);
    }
}

虽然 Lambda 表达式优点蛮多的,不过也有一些缺点,比如会增加调试困难,因为 Lambda 表达式是匿名的,在调试时很难定位具体是哪个 Lambda 表达式出现了问题。尤其是当 Lambda 表达式嵌套使用或者比较复杂时,调试难度会进一步增加。

说说 HashMap 的底层原理?

在 JDK 1.7 版本之前, HashMap 数据结构是数组和链表,HashMap通过哈希算法将元素的键(Key)映射到数组中的槽位(Bucket)。如果多个键映射到同一个槽位,它们会以链表的形式存储在同一个槽位上,因为链表的查询时间是O(n),所以冲突很严重,一个索引上的链表非常长,效率就很低了。

所以在 JDK 1.8 版本的时候做了优化,当一个链表的长度超过8的时候就转换数据结构,不再使用链表存储,而是使用红黑树,查找时使用红黑树,时间复杂度O(log n),可以提高查询性能,但是在数量较少时,即数量小于6时,会将红黑树转换回链表。

Docker 有了解吗?

了解过,大家一起开发一个软件时,每个人的电脑就像不同的房子,里面的家具(软件、工具等环境配置)都不太一样。用 Docker 就可以打造一个标准的 “样板房”,不管谁拿到这个 “样板房”(Docker 镜像),里面的东西都是一样的。这样开发时就不会因为环境不同,出现代码在这个人电脑上能跑,在另一个人电脑上就出错的情况。

docker 底层的隔离实现原理如下:

  • 基于 Namespace 的视图隔离:Docker利用Linux命名空间(Namespace)来实现不同容器之间的隔离。每个容器都运行在自己的一组命名空间中,包括PID(进程)、网络、挂载点、IPC(进程间通信)等。这样,容器中的进程只能看到自己所在命名空间内的进程,而不会影响其他容器中的进程。

  • 基于 cgroups 的资源隔离:cgroups 是Linux内核的一个功能,允许在进程组之间分配、限制和优先处理系统资源,如CPU、内存和磁盘I/O。它们提供了一种机制,用于管理和隔离进程集合的资源使用,有助于资源限制、工作负载隔离以及在不同进程组之间进行资源优先处理。

Jenkins 有了解吗?讲讲什么是持续集成和持续部署?

Jenkins 是开源的自动化服务器,核心功能是实现 CI/CD 流程的调度和管理。例子,一个软件开发团队使用 Jenkins 作为 CI/CD 工具,开发人员每天多次将代码提交到 Git 仓库,Jenkins 会自动触发构建和测试任务。如果代码通过了所有测试,Jenkins 会将代码自动部署到生产环境,让用户能够尽快使用到新的功能。

持续集成(Continuous Integration,CI)

持续集成是什么?开发人员频繁地将代码变更合并到主分支(如每天多次),每次合并后自动触发构建、测试和代码检查,确保新代码不会破坏已有功能。核心目标:快速发现并修复集成问题,避免“集成地狱”。

关键流程:

  1. 代码提交 → 触发自动化流程。

  2. 代码编译 → 检查语法错误。

  3. 运行单元测试 → 确保新代码不影响现有功能。

  4. 代码质量检查 → 如 SonarQube 分析代码规范。

  5. 生成报告 → 失败时通知团队,成功则生成可部署的产物(如 Jar 包)。

持续部署(Continuous Deployment,CD)

持续部署是什么?自动化将代码发布到生产环境,只要通过 CI 流程的代码变更,无需人工干预即可直接上线。核心目标:快速、安全地交付用户价值。

关键流程:

  1. 从 CI 产物出发 → 如通过测试的 Jar 包。

  2. 环境部署 → 自动部署到测试、预发布、生产环境。

  3. 自动化验收测试 → 验证功能是否符合需求。

  4. 监控与回滚 → 发现生产问题自动回退版本。

假设团队开发一个 Java Web 项目:

  • 持续集成(CI:开发者提交代码到 Git → Jenkins 自动拉取代码 → 运行 Maven 构建 → 执行单元测试 → 生成测试报告。如果测试失败,邮件通知负责人;成功则生成 War 包。

  • 持续部署(CD:先将 War 包自动部署到 Tomcat 服务器,然后使用 Docker 或 Kubernetes 实现滚动更新,零停机部署。

项目问题

  • 你觉得这个项目做起来最难的地方是什么?

  • 你们团队有多少人完成这个项目?

  • 你的项目最高同时在线人数有多少?项目是怎么部署的?

聊天问题

  • 你什么时候开始接触 Java 的?

  • 你本科专业不是计算机,本科学过编程吗?

  • 你是哪里人?研究生的课题是什么?过来的话打算租房子吗?

面试感受

听同学说,海尔这次的面试体验相当不错。面试官特别靠谱,早早地就进会议了,开场就说别紧张,就当唠唠嗑,语气那叫一个温和亲切。面试的时候,还主动跟同学聊起租房子的事,给了好多实用的建议,这波好感拉满了。

2
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin
  3. QQ打赏

    qrcode qq

评论区