互联网大厂面试:Java核心知识、框架与中间件大考验
王铁牛怀揣着紧张与期待,坐在了互联网大厂的面试室里,对面的面试官表情严肃,一场考验拉开了帷幕。
第一轮提问 面试官:首先问几个 Java 核心知识的问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那 Java 里的自动装箱和拆箱是怎么回事? 王铁牛:自动装箱就是把基本数据类型转换成对应的包装类,拆箱就是反过来,把包装类转换成基本数据类型。 面试官:很好。那说说 Java 中 String、StringBuffer 和 StringBuilder 的区别。 王铁牛:String 是不可变的,每次对 String 的操作都会生成新的 String 对象。StringBuffer 是可变的,而且它是线程安全的,方法有加 synchronized 关键字。StringBuilder 也是可变的,但它不是线程安全的,不过性能比 StringBuffer 要好一些。 面试官:回答得非常棒,看来基础很扎实。接下来进入多线程相关的问题。什么是线程安全? 王铁牛:线程安全就是在多线程环境下,对共享资源的访问不会出现数据不一致或者其他异常情况,保证程序的正确性。
第二轮提问 面试官:现在到 JUC 相关知识了。JUC 里的 CountDownLatch 是做什么用的? 王铁牛:呃……这个好像是用来控制线程同步的,具体的我有点不太记得清楚了。 面试官:那说说 JVM 的内存模型,有哪些区域? 王铁牛:有堆、栈、方法区,还有……对了,还有本地方法栈和程序计数器。不过具体每个区域存什么我有点乱。 面试官:线程池的作用是什么?常见的线程池有哪些? 王铁牛:线程池可以复用线程,减少线程创建和销毁的开销。常见的……好像有固定大小线程池,还有其他的我就不太确定了。 面试官:HashMap 的底层数据结构是什么?它是如何解决哈希冲突的? 王铁牛:底层好像是数组加链表,解决哈希冲突……就是链表吧,具体怎么弄的我不太清楚。
第三轮提问 面试官:Spring 框架里的 IoC 和 AOP 是什么? 王铁牛:IoC 是控制反转,AOP 是面向切面编程,但是具体的实现原理我不太能说清楚。 面试官:Spring Boot 有什么优势? 王铁牛:它好像能快速搭建项目,减少配置,但是具体的细节我不太了解。 面试官:MyBatis 中 #{} 和 ${} 的区别是什么? 王铁牛:这个……我只知道一个是预编译,一个不是,具体哪个是哪个我记混了。 面试官:Dubbo 是做什么的?它的工作原理是怎样的? 王铁牛:Dubbo 是个分布式服务框架,原理……我就不太懂了。 面试官:RabbitMQ、xxl - job 和 Redis 你都了解哪些?说说它们的应用场景。 王铁牛:RabbitMQ 好像是消息队列,xxl - job 是分布式任务调度,Redis 可以做缓存。但是具体怎么用我不太清楚。
面试官:今天的面试就到这里,你先回家等通知吧。从面试情况来看,你对一些基础的 Java 核心知识掌握得还可以,在回答简单问题时表现不错,说明有一定的知识储备。但对于一些稍微复杂的知识点,比如 JUC、JVM 的深入原理、框架的实现细节等,回答得不够清晰准确,还存在很多知识盲区。我们后续会综合评估所有面试者的情况,再决定是否录用。希望你如果没有通过这次面试,也能继续学习提升自己的技术水平。
答案详解
- Java 基本数据类型:Java 中有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(只有 true 和 false 两个值)。
- 自动装箱和拆箱:自动装箱是 Java 编译器在基本数据类型和对应的包装类之间做的一个转换,例如把 int 类型的 10 自动转换成 Integer 类型的对象,代码示例:
Integer i = 10;。拆箱则相反,把包装类对象转换成基本数据类型,如int j = i;。 - String、StringBuffer 和 StringBuilder 的区别:
- String:是不可变的,一旦创建,其值不能被修改。每次对 String 进行操作,如拼接,都会创建一个新的 String 对象,所以频繁操作 String 会产生很多临时对象,影响性能。
- StringBuffer:是可变的,它的方法是线程安全的,因为方法上加了 synchronized 关键字,保证了在多线程环境下操作的安全性,但性能相对较低。
- StringBuilder:也是可变的,但是它不是线程安全的,不过在单线程环境下,性能比 StringBuffer 要好,因为没有同步的开销。
- 线程安全:在多线程环境下,当多个线程同时访问共享资源时,如果不会出现数据不一致、数据损坏或者其他异常情况,就称这个资源或者操作是线程安全的。例如,一个计数器,如果多个线程同时对它进行自增操作,可能会出现数据不一致的问题,要保证它线程安全,就需要进行同步处理。
- CountDownLatch:它是 JUC 里的一个同步工具类,用于控制线程的同步。它可以让一个或多个线程等待其他线程完成一组操作后再继续执行。例如,有一个主线程需要等待多个子线程完成任务后再继续执行,就可以使用 CountDownLatch。主线程调用
await()方法进入等待状态,子线程完成任务后调用countDown()方法,当计数器减到 0 时,主线程被唤醒继续执行。 - JVM 内存模型区域:
- 堆:是 JVM 中最大的一块内存区域,用于存储对象实例,几乎所有的对象实例都在这里分配内存。堆是所有线程共享的,垃圾回收主要就是针对堆进行的。
- 栈:每个线程都有自己的栈,栈中存储局部变量、方法调用信息等。栈中的数据是线程私有的,每个方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈等信息。
- 方法区:用于存储类的信息、常量、静态变量等。它也是所有线程共享的。
- 本地方法栈:与栈类似,不过它是为本地方法服务的,本地方法是用其他语言(如 C、C++)实现的方法。
- 程序计数器:是一个较小的内存区域,它可以看作是当前线程所执行的字节码的行号指示器,每个线程都有自己独立的程序计数器。
- 线程池的作用和常见线程池:
- 作用:线程池可以复用线程,减少线程创建和销毁的开销,提高系统的性能和稳定性。它还可以控制线程的数量,避免创建过多线程导致系统资源耗尽。
- 常见线程池:
- FixedThreadPool:固定大小的线程池,线程数量是固定的,当有任务提交时,如果有空闲线程就执行任务,没有则将任务放入队列等待。
- CachedThreadPool:可缓存的线程池,线程数量不固定,当有新任务提交时,如果有空闲线程就使用,没有则创建新线程。如果线程空闲时间超过一定时间,会被回收。
- SingleThreadExecutor:单线程的线程池,只有一个线程执行任务,保证任务按顺序执行。
- ScheduledThreadPool:可以执行定时任务和周期性任务的线程池。
- HashMap 底层数据结构和解决哈希冲突的方法:
- 底层数据结构:JDK 1.8 之前是数组 + 链表,JDK 1.8 及以后是数组 + 链表 + 红黑树。数组是 HashMap 的主体,每个数组元素称为一个桶(bucket),链表和红黑树是为了解决哈希冲突而存在的。
- 解决哈希冲突的方法:当不同的键通过哈希函数计算得到相同的哈希值时,就会发生哈希冲突。HashMap 采用链地址法解决哈希冲突,即把发生哈希冲突的元素存储在同一个桶对应的链表或红黑树中。当链表长度达到一定阈值(默认是 8)且数组长度达到 64 时,链表会转换为红黑树,以提高查找效率。
- Spring 的 IoC 和 AOP:
- IoC(控制反转):是一种设计思想,将对象的创建和依赖关系的管理从代码中转移到 Spring 容器中。传统的方式是在代码中手动创建对象和管理依赖关系,而 IoC 是由 Spring 容器来负责创建和注入对象,实现了对象之间的解耦。例如,一个 Service 类依赖一个 Dao 类,在 IoC 模式下,Spring 容器会创建 Dao 对象并注入到 Service 类中。
- AOP(面向切面编程):是对面向对象编程的一种补充,它可以在不修改原有业务逻辑的基础上,对程序进行增强。例如,在方法执行前后添加日志记录、事务管理等功能。AOP 通过切面(Aspect)、切点(Pointcut)和通知(Advice)等概念来实现。
- Spring Boot 的优势:
- 快速搭建项目:Spring Boot 提供了大量的 Starter 依赖,只需要添加相应的依赖,就可以快速搭建一个项目,减少了繁琐的配置。
- 自动配置:Spring Boot 会根据项目中添加的依赖自动进行配置,开发者只需要进行少量的配置或者不配置就可以让项目运行起来。
- 嵌入式服务器:Spring Boot 内置了 Tomcat、Jetty 等嵌入式服务器,不需要额外部署服务器,直接运行项目即可。
- 监控和管理:Spring Boot Actuator 提供了项目的监控和管理功能,如查看系统信息、健康检查等。
- MyBatis 中 #{} 和 ${} 的区别:
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符
?,然后使用 PreparedStatement 进行预编译,这样可以防止 SQL 注入攻击。例如,SELECT * FROM user WHERE id = #{id}会被预编译为SELECT * FROM user WHERE id =?。 - {} 时,会直接将 {tableName}
会直接将${tableName}` 替换为实际的表名。
- #{}:是预编译处理,MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为占位符
- Dubbo 的作用和工作原理:
- 作用:Dubbo 是一个高性能的分布式服务框架,用于实现服务的远程调用和服务治理。它可以将不同的服务进行拆分,提高系统的可扩展性和可维护性。
- 工作原理:Dubbo 主要由服务提供者(Provider)、服务消费者(Consumer)、注册中心(Registry)和监控中心(Monitor)组成。服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,然后通过远程调用的方式调用服务提供者的服务。监控中心负责收集服务的调用信息,用于监控和统计。
- RabbitMQ、xxl - job 和 Redis 的应用场景:
- RabbitMQ:是一个消息队列,主要应用场景有异步处理、解耦系统、流量削峰等。例如,在电商系统中,用户下单后,将订单信息发送到 RabbitMQ 队列,然后由其他服务异步处理订单,这样可以提高系统的响应速度。
- xxl - job:是一个分布式任务调度平台,用于实现定时任务和分布式任务的调度。例如,每天凌晨定时执行数据备份任务,或者在多个服务器上分布式执行数据处理任务。
- Redis:是一个高性能的键值对数据库,主要应用场景有缓存、分布式锁、消息队列、计数器等。例如,将经常访问的数据缓存到 Redis 中,减少对数据库的访问压力;使用 Redis 的分布式锁来保证在分布式环境下数据的一致性。