互联网大厂面试:Java核心知识、JUC、JVM等全方位大考察
王铁牛怀揣着紧张又期待的心情,走进了这家互联网大厂的面试间。严肃的面试官正襟危坐,一场关于Java技术的严峻考验即将拉开帷幕。
第一轮面试 面试官:首先问你几个基础问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那说说 ArrayList 和 LinkedList 的区别。 王铁牛:ArrayList 是基于数组实现的,随机访问速度快,但是插入和删除操作效率低;LinkedList 是基于链表实现的,插入和删除操作效率高,随机访问速度慢。 面试官:回答得很好。再问你,HashMap 的底层数据结构是什么? 王铁牛:HashMap 底层是数组 + 链表 + 红黑树。当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树,以提高查找效率。 面试官:非常棒,基础掌握得很扎实。
第二轮面试 面试官:接下来聊聊 JUC 和多线程相关的。什么是线程池?为什么要使用线程池? 王铁牛:线程池就是管理线程的一个池子,使用线程池可以避免频繁创建和销毁线程带来的性能开销,还能控制线程的数量,提高系统的稳定性。 面试官:很好。那线程池有哪些创建方式? 王铁牛:可以通过 Executors 工具类创建,像 newFixedThreadPool、newCachedThreadPool 等,不过阿里巴巴开发手册不推荐用 Executors 创建,建议使用 ThreadPoolExecutor 手动创建。 面试官:回答正确。那说说 CountDownLatch 和 CyclicBarrier 的区别。 王铁牛:这个……嗯……好像都是和线程同步有关的,具体区别我有点说不太清楚了。 面试官:没关系,这确实有一定难度。简单来说,CountDownLatch 是一个计数器,线程完成任务后计数器减一,当计数器为 0 时,等待的线程继续执行;CyclicBarrier 是让一组线程相互等待,当所有线程都到达屏障点时,所有线程才会继续执行,而且它可以循环使用。
第三轮面试 面试官:现在来谈谈框架相关的。Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性是 IOC(控制反转)和 AOP(面向切面编程)。IOC 是把对象的创建和依赖关系的管理交给 Spring 容器,AOP 是在不修改源代码的情况下,对程序进行增强。 面试官:回答得不错。那 Spring Boot 相对于 Spring 有什么优势? 王铁牛:Spring Boot 简化了 Spring 的配置,提供了很多默认配置,能快速搭建项目,还内置了 Tomcat 等服务器,方便开发和部署。 面试官:很好。MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:#{} 是预编译处理,能防止 SQL 注入,{} 是直接替换,会有 SQL 注入风险。 面试官:回答正确。Dubbo 是做什么的? 王铁牛:Dubbo 是一个高性能的 Java RPC 框架,用于实现服务的远程调用。 面试官:那 RabbitMQ 有哪些使用场景? 王铁牛:这个……我记得可以用于异步处理、流量削峰之类的,具体怎么实现我不太清楚了。
面试结束,面试官看着王铁牛说:“今天的面试就到这里,你整体基础还是比较扎实的,对于一些基础的 Java 知识和常见框架的基本概念掌握得不错,但是在一些稍微复杂的 JUC 同步工具以及 RabbitMQ 使用场景细节上还有所欠缺。你先回家等通知吧,后续如果有结果会及时联系你。”
问题答案详解
- Java 中基本数据类型有哪些?
Java 中有 8 种基本数据类型,分别为:
- 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)。
- 浮点类型:float(4 字节)、double(8 字节)。
- 字符类型:char(2 字节)。
- 布尔类型:boolean(理论上 1 位,但实际实现通常占 1 字节)。
- ArrayList 和 LinkedList 的区别
- 数据结构:ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。
- 随机访问:ArrayList 可以通过索引直接访问元素,时间复杂度为 O(1);LinkedList 需要从头或尾开始遍历链表,时间复杂度为 O(n)。
- 插入和删除:ArrayList 在中间插入或删除元素时,需要移动后续元素,时间复杂度为 O(n);LinkedList 插入和删除元素只需修改相邻节点的指针,时间复杂度为 O(1)(如果是指定位置插入,需要先定位到该位置,时间复杂度为 O(n))。
- 内存占用:ArrayList 会预留一定的空间,可能会有空间浪费;LinkedList 每个节点需要额外的指针来指向前驱和后继节点,占用空间相对较大。
- HashMap 的底层数据结构 HashMap 底层是数组 + 链表 + 红黑树。数组被称为哈希桶,每个桶存放一个链表或红黑树。当有新元素插入时,首先根据元素的哈希值计算出在数组中的位置,如果该位置为空,则直接插入;如果该位置已经有元素,会比较元素的键是否相等,如果相等则覆盖,否则将元素插入到链表或红黑树中。当链表长度大于 8 且数组长度大于 64 时,链表会转化为红黑树,以提高查找效率;当红黑树节点数小于 6 时,会退化为链表。
- 什么是线程池?为什么要使用线程池?
线程池是一种线程使用模式,它预先创建一定数量的线程,当有任务提交时,从线程池中获取线程来执行任务,任务执行完后线程不会销毁,而是返回线程池等待下一个任务。使用线程池的原因有:
- 降低资源消耗:避免频繁创建和销毁线程带来的性能开销。
- 提高响应速度:任务到达时,直接从线程池中获取线程执行,无需等待线程创建。
- 方便管理:可以控制线程的数量、线程的生命周期等,提高系统的稳定性。
- 线程池有哪些创建方式?
- 通过 Executors 工具类创建:
- newFixedThreadPool:创建一个固定大小的线程池,线程数量固定,当有新任务提交时,如果线程池中有空闲线程,则执行任务,否则任务会在队列中等待。
- newCachedThreadPool:创建一个可缓存的线程池,线程数量不固定,当有新任务提交时,如果线程池中有空闲线程,则执行任务,否则创建新线程执行任务。当线程空闲时间超过 60 秒时,会被销毁。
- newSingleThreadExecutor:创建一个单线程的线程池,保证所有任务按照顺序执行。
- newScheduledThreadPool:创建一个定时任务线程池,用于执行定时任务和周期性任务。
- 使用 ThreadPoolExecutor 手动创建:通过构造函数指定线程池的核心线程数、最大线程数、线程空闲时间、任务队列等参数,这种方式可以更好地控制线程池的行为,避免资源耗尽等问题。
- 通过 Executors 工具类创建:
- CountDownLatch 和 CyclicBarrier 的区别
- CountDownLatch:是一个计数器,它的构造函数需要传入一个初始计数值。当一个线程完成任务后,调用 countDown() 方法将计数器减一,其他线程可以调用 await() 方法等待计数器变为 0。计数器一旦变为 0,就不能再恢复,常用于一个或多个线程等待其他线程完成任务的场景。
- CyclicBarrier:让一组线程相互等待,当所有线程都到达屏障点时,所有线程才会继续执行。它的构造函数需要传入一个参与线程的数量和一个可选的屏障动作。当线程调用 await() 方法时,会进入等待状态,当所有线程都调用了 await() 方法后,屏障会打开,所有线程继续执行,并且可以重置屏障,再次使用,常用于多个线程相互协作完成一个任务的场景。
- Spring 的核心特性有哪些?
- IOC(控制反转):将对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象自己创建或管理依赖。通过依赖注入(DI)的方式,将依赖对象注入到需要的对象中,降低了对象之间的耦合度。
- AOP(面向切面编程):在不修改源代码的情况下,对程序进行增强。通过将横切关注点(如日志记录、事务管理等)封装成切面,在程序运行时将切面织入到目标对象中,提高了代码的可维护性和复用性。
- Spring Boot 相对于 Spring 有什么优势?
- 简化配置:Spring Boot 提供了很多默认配置,大部分情况下不需要手动配置,减少了繁琐的 XML 配置文件或 Java 配置类。
- 快速搭建项目:通过 Spring Initializr 可以快速生成项目骨架,集成了各种常用的依赖,提高了开发效率。
- 内置服务器:内置了 Tomcat、Jetty 等服务器,无需额外配置和部署服务器,方便开发和测试。
- 自动配置:根据项目中引入的依赖,Spring Boot 会自动进行配置,例如引入 Spring Data JPA 依赖后,会自动配置数据源和 JPA 相关的配置。
- MyBatis 中 #{} 和 ${} 的区别是什么?
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符 ?,然后使用 PreparedStatement 进行预编译,防止 SQL 注入。
- **{} 时,会将 ${} 直接替换为参数的值,可能会导致 SQL 注入问题,通常用于动态表名、列名等场景。
- Dubbo 是做什么的? Dubbo 是一个高性能的 Java RPC(远程过程调用)框架,用于实现服务的远程调用。它可以让不同的服务之间像本地调用一样进行交互,屏蔽了网络通信的细节。Dubbo 提供了服务注册与发现、集群容错、负载均衡等功能,提高了服务的可用性和可扩展性,常用于分布式系统中服务之间的通信。
- RabbitMQ 有哪些使用场景?
- 异步处理:将一些耗时的操作(如发送邮件、生成报表等)放到消息队列中异步处理,提高系统的响应速度。例如,用户注册成功后,系统可以将发送欢迎邮件的任务发送到 RabbitMQ 队列中,由专门的消费者线程处理,而不需要用户等待邮件发送完成。
- 流量削峰:在高并发场景下,将请求放入消息队列中,控制消费者的消费速度,避免系统因瞬间高流量而崩溃。例如,电商系统在促销活动期间,大量用户同时下单,将订单请求放入 RabbitMQ 队列中,系统按照一定的速度处理订单,保证系统的稳定性。
- 系统解耦:不同的系统或模块之间通过消息队列进行通信,降低了系统之间的耦合度。例如,订单系统和库存系统之间通过 RabbitMQ 进行消息传递,订单系统下单成功后发送消息到队列,库存系统从队列中获取消息并更新库存,两个系统不需要直接交互,提高了系统的可维护性和可扩展性。
- 日志收集:将系统产生的日志信息发送到 RabbitMQ 队列中,由专门的日志处理系统从队列中获取日志进行处理和存储,方便对日志进行统一管理和分析。