《揭秘互联网大厂Java面试:从基础到进阶的核心知识大考察》

30 阅读5分钟

在一个宽敞明亮却又透着几分严肃的面试房间里,一场决定求职者命运的Java面试正在进行。

面试官:“第一轮提问开始。先说说Java中ArrayList和HashMap的底层数据结构分别是什么?” 王铁牛:“ArrayList底层是数组,HashMap底层是数组加链表,JDK1.8 后引入了红黑树。” 面试官:“回答得不错。那ArrayList在扩容时具体是怎么操作的?” 王铁牛:“当ArrayList的元素个数达到容量阈值时,会进行扩容,新容量是原容量的1.5倍,然后把原数组的元素复制到新数组。” 面试官:“很好。HashMap在什么情况下会发生哈希冲突,怎么解决哈希冲突的?” 王铁牛:“当不同的键计算出相同的哈希值时就会发生哈希冲突。JDK1.8之前通过链表解决,之后在链表长度大于8且数组长度大于64时,链表会转化为红黑树来解决。”

面试官:“第二轮提问。讲讲Spring的IOC和AOP是什么?” 王铁牛:“IOC就是控制反转,把对象的创建和管理交给Spring容器。AOP是面向切面编程,在不修改原有代码的基础上,对方法进行增强。” 面试官:“那Spring Boot相比Spring,主要优势在哪?” 王铁牛:“Spring Boot简化了Spring应用的搭建和开发过程,它有自动配置功能,能快速构建独立的、生产级别的Spring应用。” 面试官:“MyBatis中#{}和{}有什么区别?” **王铁牛**:“呃……#{}是预编译处理,{}是字符串替换,#{}能防止SQL注入,${}一般用于传入数据库对象,比如表名。”

面试官:“第三轮提问。Dubbo的服务调用流程是怎样的?” 王铁牛:“嗯……就是服务提供者把服务注册到注册中心,服务消费者从注册中心获取服务列表,然后进行调用。” 面试官:“RabbitMQ的消息确认机制是怎么回事?” 王铁牛:“就是……生产者发送消息后,RabbitMQ会给生产者返回确认信息,确认消息是否成功接收。” 面试官:“xxl - job的核心原理是什么?” 王铁牛:“它好像是……通过调度中心来管理任务,然后执行器去执行任务。” 面试官:“Redis有哪些数据类型,ZSet类型有什么特点?” 王铁牛:“有String、Hash、List、Set、ZSet。ZSet是有序集合,每个元素都关联一个分数,根据分数来排序。”

面试官思索片刻后说道:“今天的面试就到这里,你的表现有亮点也有不足。后续我们会综合评估所有候选人,你回家等通知吧,无论结果如何,我们都会在一周内给你回复。感谢你今天来参加面试。”

答案

  1. ArrayList和HashMap的底层数据结构
    • ArrayList:底层是数组结构,它允许以数组下标的方式快速访问元素。例如,ArrayList<Integer> list = new ArrayList<>(); list.add(10); int num = list.get(0);这里通过get(0)能快速获取到第一个元素,因为它基于数组存储。
    • HashMap:JDK1.8之前底层是数组加链表结构,数组的每个位置是一个链表头节点。当发生哈希冲突时,新的键值对会以链表的形式挂在对应数组位置的链表上。JDK1.8引入红黑树后,当链表长度大于8且数组长度大于64时,链表会转化为红黑树,以提高查找效率。例如,HashMap<String, Integer> map = new HashMap<>(); map.put("key1", 1);通过哈希算法计算出“key1”的哈希值,找到对应的数组位置,如果该位置为空则直接插入,如果不为空则判断是否哈希冲突等情况进行处理。
  2. ArrayList扩容操作
    • ArrayList有一个容量(capacity)和实际元素个数(size)。当size达到capacity的阈值(一般是capacity本身)时,会触发扩容。新容量是原容量的1.5倍(newCapacity = oldCapacity + (oldCapacity >> 1))。然后会创建一个新的更大的数组,通过System.arraycopy()方法将原数组的元素复制到新数组中。例如,初始容量为10的ArrayList,当添加第11个元素时就会触发扩容。
  3. HashMap哈希冲突及解决
    • 哈希冲突:由于哈希算法的局限性,不同的键可能计算出相同的哈希值,这就产生了哈希冲突。例如,两个不同的字符串“abc”和“cba”经过哈希算法可能得到相同的哈希值。
    • 解决方法:JDK1.8之前主要通过链表法解决,即把冲突的键值对以链表的形式挂在数组同一位置。JDK1.8后,当链表长度大于8且数组长度大于64时,链表会转化为红黑树,因为红黑树的查找、插入、删除平均时间复杂度为O(log n),比链表的O(n)在大数据量下效率更高。
  4. Spring的IOC和AOP
    • IOC(控制反转):传统开发中,对象的创建和管理由应用程序自身负责,耦合度高。IOC则把对象的创建、初始化、销毁等控制权交给Spring容器。例如,有一个UserService类,传统方式是UserService userService = new UserService();,在Spring中通过配置或注解,由Spring容器创建和管理UserService实例,应用程序只需要使用即可,降低了组件之间的耦合度。
    • AOP(面向切面编程):它将一些通用功能(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,以切面的形式织入到目标方法中。例如,在一个电商系统中,订单处理方法可能需要在执行前后记录日志,通过AOP可以在不修改订单处理方法代码的情况下,添加日志记录功能。
  5. Spring Boot相比Spring的优势
    • 自动配置:Spring Boot基于约定大于配置的原则,能根据项目中的依赖自动配置Spring应用。例如,添加了spring - boot - starter - jdbc依赖,Spring Boot会自动配置数据源、JdbcTemplate等,开发者无需像在Spring中那样进行大量繁琐的XML配置或Java配置。
    • 快速搭建:可以快速构建独立的、生产级别的Spring应用,内置了Tomcat、Jetty等服务器,直接打包成可执行的jar或war文件就能运行。例如,使用Spring Initializr快速生成一个Spring Boot项目骨架,几分钟内就能搭建一个基本可用的Web应用。
  6. MyBatis中#{}和${}的区别
    • #{}:是预编译处理,MyBatis在处理#{}时,会将SQL中的#{}替换为?,然后使用PreparedStatementset方法来设置参数值,这样能有效防止SQL注入。例如,select * from user where username = #{username},实际执行时会是select * from user where username =?,然后通过PreparedStatement.setString(1, "具体用户名")设置参数。
    • **:是字符串替换,MyBatis在处理{}**:是字符串替换,MyBatis在处理`{}时,会直接将替换为变量的值。例如,selectfrom{}`替换为变量的值。例如,`select * from {tableName},如果tableName的值是“user”,则实际执行的SQL就是select * from user`。由于它是直接替换,所以存在SQL注入风险,一般用于传入数据库对象(如表名、列名等)。
  7. Dubbo的服务调用流程
    • 服务注册:服务提供者启动时,将自己提供的服务注册到注册中心(如Zookeeper),注册中心记录服务提供者的地址、端口等信息。
    • 服务订阅:服务消费者启动时,向注册中心订阅自己需要的服务,注册中心返回服务提供者的列表给服务消费者。
    • 服务调用:服务消费者从服务提供者列表中选择一个提供者进行调用,调用过程中可以通过负载均衡算法(如随机、轮询等)选择具体的服务实例。例如,电商系统中,商品服务作为服务提供者,订单服务作为服务消费者,订单服务需要调用商品服务获取商品信息,就按照上述流程进行。
  8. RabbitMQ的消息确认机制
    • 生产者确认:生产者发送消息后,RabbitMQ会给生产者返回确认信息,确认消息是否成功接收。有两种确认模式:
      • 普通确认模式:生产者发送一条消息后,调用channel.waitForConfirms()方法等待RabbitMQ的确认,这种方式性能较低,因为是同步等待。
      • 批量确认模式:生产者发送一批消息后,调用channel.waitForConfirmsOrDie()方法等待确认,相比普通确认模式,减少了等待次数,提高了性能。
      • 异步确认模式:生产者通过channel.addConfirmListener()添加确认监听器,当RabbitMQ确认消息后,会回调监听器的方法,这种方式性能最高,因为生产者无需阻塞等待确认。
    • 消费者确认:消费者接收消息后,需要向RabbitMQ发送确认消息,告知RabbitMQ该消息已被成功处理。如果消费者没有发送确认消息,RabbitMQ会认为消息没有被成功处理,可能会重新发送该消息给其他消费者或重新放入队列。
  9. xxl - job的核心原理
    • 调度中心:负责管理任务,包括任务的新增、修改、删除、暂停、恢复等操作。同时,调度中心按照设定的调度规则触发任务执行。例如,可以在调度中心设置一个任务每天凌晨1点执行。
    • 执行器:部署在各个业务服务器上,负责接收调度中心发送的任务并执行。执行器启动时会向调度中心注册自己,调度中心根据执行器的注册信息将任务发送给对应的执行器。例如,在电商系统中,订单过期处理任务可以由专门的执行器执行,调度中心按照规则触发任务,执行器接收并处理。
  10. Redis的数据类型及ZSet特点
  • 数据类型
    • String:最基本的数据类型,能存储字符串、数字、二进制数据等。例如,set key1 value1存储一个字符串。
    • Hash:用于存储对象,以字段和值的形式存储。例如,hset user:1 name "张三" age 20存储一个用户对象。
    • List:是一个链表结构,支持两端插入和删除,可用于实现队列、栈等数据结构。例如,lpush list1 value1 value2从列表头部插入元素。
    • Set:无序集合,元素不重复。例如,sadd set1 value1 value2添加元素到集合。
    • ZSet:有序集合,每个元素都关联一个分数(score),根据分数来排序。例如,zadd zset1 10 value1 20 value2value1的分数是10,value2的分数是20,在获取元素时会根据分数排序。
  • ZSet特点
    • 有序性:通过分数来保证元素的有序性,适用于排行榜等场景,如游戏玩家的积分排行榜。
    • 可重复性:元素本身不能重复,但分数可以相同,例如多个玩家积分相同。
    • 范围查询高效:可以根据分数范围快速查询元素,如查询积分在100 - 200之间的玩家。