《互联网大厂面试:Java核心知识、JUC、JVM等技术大考验》

80 阅读10分钟

互联网大厂面试:Java核心知识、JUC、JVM等技术大考验

在互联网大厂的一间安静的面试室内,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。一场关于Java技术的严峻考验即将拉开帷幕。

第一轮面试开始 面试官:首先问你几个基础问题。Java 里的基本数据类型有哪些? 王铁牛:嗯,有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那说说 HashMap 的底层数据结构是什么? 王铁牛:HashMap 底层是数组 + 链表 + 红黑树。当链表长度超过 8 且数组长度大于 64 时,链表会转化为红黑树。 面试官:可以,基础很扎实。那 ArrayList 是如何实现动态扩容的? 王铁牛:ArrayList 有一个初始容量,当添加元素超过容量时,会创建一个新的数组,新数组容量是原数组的 1.5 倍,然后把原数组元素复制到新数组。

面试官:很好,看来你对这些基础的数据结构掌握得很清晰。

第二轮面试开始 面试官:接下来聊聊多线程相关的。什么是线程池,为什么要使用线程池? 王铁牛:线程池就是管理一组线程的容器。使用线程池可以减少线程创建和销毁的开销,提高性能,还能控制并发线程的数量。 面试官:不错。那 JUC 包下的 CountDownLatch 是做什么用的,举个使用场景。 王铁牛:CountDownLatch 可以让一个或多个线程等待其他线程完成操作。比如一个主线程要等待多个子线程完成数据加载后再继续执行,就可以用 CountDownLatch。 面试官:有一定理解。那在多线程环境下,如何保证线程安全的使用 HashMap? 王铁牛:额……可以用 Collections.synchronizedMap 把 HashMap 包装一下,或者用 ConcurrentHashMap。

面试官:回答得还行,对多线程的一些工具和安全问题有自己的思考。

第三轮面试开始 面试官:现在说说框架相关的。Spring 的 IOC 和 AOP 是什么,有什么作用? 王铁牛:IOC 是控制反转,把对象的创建和依赖关系的管理交给 Spring 容器,降低了代码的耦合度。AOP 是面向切面编程,能在不修改原有代码的情况下,对程序进行增强,比如实现日志记录、事务管理等。 面试官:解释得比较清楚。那 Spring Boot 和 Spring 有什么区别和联系? 王铁牛:Spring Boot 是基于 Spring 的,它简化了 Spring 的配置,提供了很多默认配置,能快速搭建项目,提高开发效率。 面试官:有一定认知。那 MyBatis 是如何实现 SQL 语句和 Java 代码的映射的? 王铁牛:这个……好像是通过 XML 文件或者注解来定义 SQL 语句和 Java 对象属性的映射关系。

面试官:回答得还算沾边。那最后问你,Dubbo、RabbitMq、xxl - job、Redis 这些技术在实际业务场景中都有什么作用? 王铁牛:Dubbo 是分布式服务框架,能实现服务的注册和发现;RabbitMq 是消息队列,可用于异步通信、解耦;xxl - job 是分布式任务调度平台;Redis 是缓存数据库,能提高系统的读写性能。不过具体怎么用我有点说不太清楚。

面试官:好了,今天的面试就到这里。你先回家等通知吧,后续我们会综合评估后给你反馈。

问题答案详解

  1. Java 里的基本数据类型有哪些? Java 有 8 种基本数据类型,分为 4 类:
    • 整数类型:byte(1 字节,-128 到 127)、short(2 字节,-32768 到 32767)、int(4 字节,-2147483648 到 2147483647)、long(8 字节,范围很大)。
    • 浮点类型:float(4 字节,单精度浮点数)、double(8 字节,双精度浮点数)。
    • 字符类型:char(2 字节,用于表示单个字符)。
    • 布尔类型:boolean(只有两个值,true 和 false)。
  2. HashMap 的底层数据结构是什么? HashMap 底层是数组 + 链表 + 红黑树的结构。数组是 HashMap 的主体,每个数组元素称为一个桶(bucket)。当有键值对插入时,通过哈希函数计算键的哈希值,然后根据哈希值找到对应的桶位置。如果该位置已经有元素,就会以链表的形式存储这些元素。当链表长度超过 8 且数组长度大于 64 时,链表会转化为红黑树,以提高查找效率,因为红黑树的查找时间复杂度是 O(log n),而链表是 O(n)。
  3. ArrayList 是如何实现动态扩容的? ArrayList 内部使用一个数组来存储元素。当创建 ArrayList 对象时,会有一个初始容量(默认是 10)。当添加元素时,如果当前数组的容量不够,就会触发扩容操作。扩容时,会创建一个新的数组,新数组的容量是原数组容量的 1.5 倍(通过位运算实现,即 oldCapacity + (oldCapacity >> 1))。然后把原数组中的元素复制到新数组中,最后将新元素添加到新数组中。
  4. 什么是线程池,为什么要使用线程池? 线程池是一种管理线程的机制,它包含一个线程集合和一个任务队列。线程池负责创建、管理和销毁线程。使用线程池的好处有:
    • 减少开销:线程的创建和销毁是比较消耗资源的操作,线程池可以复用线程,避免频繁创建和销毁线程带来的开销。
    • 提高响应速度:当有任务提交时,线程池中如果有空闲线程,就可以立即执行任务,不需要等待新线程的创建。
    • 控制并发数量:可以通过线程池的参数控制并发线程的数量,避免过多线程导致系统资源耗尽。
  5. JUC 包下的 CountDownLatch 是做什么用的,举个使用场景。 CountDownLatch 是 JUC(Java.util.concurrent)包下的一个同步工具类,它允许一个或多个线程等待其他线程完成操作。它内部有一个计数器,在创建 CountDownLatch 对象时需要指定计数器的初始值。当一个线程完成任务后,会调用 countDown() 方法将计数器减 1。其他线程可以调用 await() 方法来等待计数器变为 0,当计数器变为 0 时,等待的线程会继续执行。使用场景比如在一个大数据处理系统中,主线程需要等待多个子线程完成不同数据块的加载和处理后,再进行后续的汇总和分析操作。
  6. 在多线程环境下,如何保证线程安全的使用 HashMap? 在多线程环境下,直接使用 HashMap 是不安全的,因为它不是线程安全的类。可以采用以下两种方式保证线程安全:
    • 使用 Collections.synchronizedMap:通过 Collections.synchronizedMap 方法可以将一个普通的 HashMap 包装成一个线程安全的 Map。例如:Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
    • 使用 ConcurrentHashMap:ConcurrentHashMap 是 Java 提供的线程安全的哈希表实现,它采用分段锁或 CAS(Compare - And - Swap)等机制来保证线程安全,性能比使用 synchronizedMap 更高。
  7. Spring 的 IOC 和 AOP 是什么,有什么作用?
    • IOC(控制反转):传统的程序中,对象的创建和依赖关系的管理是由程序代码自己完成的。而在 Spring 中,IOC 把对象的创建和依赖关系的管理交给了 Spring 容器。通过 IOC,对象之间的耦合度降低,代码的可维护性和可测试性提高。例如,一个 Service 类依赖一个 DAO 类,在 IOC 模式下,Service 类不需要自己创建 DAO 对象,而是由 Spring 容器注入。
    • AOP(面向切面编程):AOP 是一种编程范式,它允许在不修改原有代码的情况下,对程序进行增强。AOP 把程序中的一些通用功能(如日志记录、事务管理、权限验证等)提取出来,形成一个个切面,然后在需要的地方进行切入。这样可以避免代码的重复,提高代码的复用性。
  8. Spring Boot 和 Spring 有什么区别和联系?
    • 联系:Spring Boot 是基于 Spring 构建的,它继承了 Spring 的核心特性,如 IOC 和 AOP 等。Spring Boot 可以使用 Spring 的所有功能。
    • 区别:Spring 需要大量的配置文件来配置各种组件,开发和部署过程相对繁琐。而 Spring Boot 提供了很多默认配置,采用约定大于配置的原则,能快速搭建项目,减少了配置的工作量,提高了开发效率。同时,Spring Boot 还提供了嵌入式服务器(如 Tomcat、Jetty 等),方便项目的部署。
  9. MyBatis 是如何实现 SQL 语句和 Java 代码的映射的? MyBatis 实现 SQL 语句和 Java 代码的映射有两种方式:
    • XML 映射文件:在 XML 文件中定义 SQL 语句和 Java 对象属性的映射关系。例如,定义一个查询语句:
<select id="getUserById" parameterType="int" resultType="com.example.User">
    SELECT * FROM users WHERE id = #{id}
</select>

这里 id 是 SQL 语句的唯一标识,parameterType 是输入参数的类型,resultType 是查询结果映射的 Java 对象类型。 - 注解方式:可以在 Java 接口的方法上使用注解来定义 SQL 语句。例如:

@Select("SELECT * FROM users WHERE id = #{id}")
User getUserById(int id);
  1. Dubbo、RabbitMq、xxl - job、Redis 这些技术在实际业务场景中都有什么作用?
    • Dubbo:是阿里巴巴开源的分布式服务框架,主要用于解决分布式系统中服务之间的调用问题。它提供了服务的注册与发现、远程调用、负载均衡等功能。在大型的分布式系统中,不同的服务可能部署在不同的服务器上,Dubbo 可以让这些服务之间方便地进行通信和调用。
    • RabbitMq:是一个开源的消息队列中间件,用于实现异步通信和解耦。在一个复杂的系统中,不同模块之间的处理速度可能不同,使用消息队列可以让这些模块之间通过消息进行异步通信,提高系统的性能和可扩展性。例如,在一个电商系统中,用户下单后,订单服务可以将订单消息发送到 RabbitMq 中,库存服务和物流服务可以从消息队列中获取订单消息进行相应的处理,这样订单服务不需要等待库存服务和物流服务处理完成,提高了系统的响应速度。
    • xxl - job:是一个分布式任务调度平台,用于管理和调度分布式系统中的定时任务。在分布式系统中,可能有多个服务需要定时执行一些任务,xxl - job 可以统一管理这些任务,提供任务的调度、监控、报警等功能。
    • Redis:是一个开源的高性能键值对数据库,通常用作缓存。它支持多种数据结构(如字符串、哈希、列表、集合、有序集合等),读写速度非常快。在实际业务中,Redis 可以用于缓存热点数据,减少数据库的访问压力,提高系统的读写性能。例如,在一个电商系统中,可以将商品的基本信息、热门商品列表等数据缓存到 Redis 中,用户访问时先从 Redis 中获取数据,如果 Redis 中没有再从数据库中获取。