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

41 阅读11分钟

在一间明亮却略显严肃的面试室里,一场决定求职者命运的Java面试即将拉开帷幕。

面试官:好,我们开始面试。第一轮,先问几个基础问题。第一个,Java中ArrayList和HashMap的底层数据结构分别是什么?

王铁牛:ArrayList底层是数组,HashMap底层是数组加链表,后来JDK 1.8引入了红黑树。

面试官:不错,回答得很准确。第二个问题,Spring框架中IOC和AOP分别是什么?

王铁牛:IOC是控制反转,把对象创建和管理的控制权交给Spring容器。AOP是面向切面编程,能在不修改原有代码的基础上增加功能,像日志记录、事务管理。

面试官:回答得很好。第三个问题,MyBatis中#{}和${}的区别是什么?

王铁牛:#{}是预编译处理,能防止SQL注入,${}是字符串替换,一般用于传入数据库对象,比如表名。

面试官:非常好,第一轮表现不错。接下来第二轮。第一个问题,多线程中线程池的核心参数有哪些,分别有什么作用?

王铁牛:嗯……有核心线程数,最大线程数,还有……还有队列容量吧,核心线程数就是一直保留的线程数,最大线程数就是最多能创建的线程数,队列容量就是存放任务的队列大小。

面试官:基本答对了。第二个问题,JUC包下的CountDownLatch和CyclicBarrier有什么区别?

王铁牛:这个……CountDownLatch是一次性的,一个线程等其他多个线程完成。CyclicBarrier好像能循环用,多个线程互相等待。

面试官:回答得不太清晰,解释得不够深入。第三个问题,Redis有哪些数据类型,分别适用于什么场景?

王铁牛:有String、Hash、List、Set、ZSet。String存简单数据,Hash存对象,List存列表数据,Set存不重复数据,ZSet带权重的不重复数据。

面试官:还行,第二轮表现有好有不足。下面第三轮。第一个问题,Dubbo的服务调用流程是怎样的?

王铁牛:嗯……就是服务提供者注册服务,消费者从注册中心获取服务,然后调用,中间好像还有负载均衡啥的。

面试官:回答得太简略了。第二个问题,RabbitMQ的工作模式有哪些,讲讲Direct模式的特点。

王铁牛:有简单模式、工作队列模式、发布订阅模式、Direct模式、Topic模式。Direct模式就是根据路由键直接把消息发到对应的队列。

面试官:回答得比较浅。第三个问题,xxl - job的核心原理是什么,它是如何实现分布式任务调度的?

王铁牛:它好像有个调度中心,还有执行器,调度中心负责调度任务,执行器执行任务,通过网络通信啥的实现分布式调度。

面试官:整体回答得不是特别理想。今天的面试就到这里,你回去等通知吧。我们会综合评估所有候选人,有消息会尽快联系你。希望你回去之后能对今天回答得不太好的知识点再深入学习一下。

问题答案

  1. ArrayList和HashMap的底层数据结构
    • ArrayList:底层是数组结构。这种结构允许快速的随机访问,因为可以通过数组下标直接定位元素。例如,list.get(5)可以直接获取到索引为5的元素。但在插入和删除元素时,如果不是在末尾操作,可能需要移动大量元素,时间复杂度较高。
    • HashMap:JDK 1.8之前,底层是数组加链表。数组的每个位置是一个链表的头节点。当发生哈希冲突时,新的元素会以链表的形式存储在该位置。JDK 1.8引入了红黑树,当链表长度超过8且数组容量大于64时,链表会转换为红黑树,以提高查找效率。红黑树是一种自平衡的二叉查找树,查找、插入和删除操作的时间复杂度平均为O(log n)。
  2. Spring框架中IOC和AOP
    • IOC(控制反转):传统开发中,对象的创建和管理由应用程序自己负责,耦合度高。IOC将对象的创建和管理控制权交给Spring容器。例如,一个Service类依赖另一个Dao类,如果没有IOC,Service类需要自己创建Dao类的实例。使用IOC后,Spring容器会创建并注入Dao实例到Service中。这样可以降低组件之间的耦合度,提高代码的可维护性和可测试性。
    • AOP(面向切面编程):它是对OOP(面向对象编程)的补充。OOP主要关注业务逻辑的封装,而AOP关注将一些通用功能(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,形成一个个切面。这些切面可以在不修改原有业务代码的基础上,通过动态代理等技术织入到目标方法的执行过程中。例如,在一个业务方法执行前后记录日志,就可以通过AOP实现。
  3. MyBatis中#{}和${}的区别
    • #{}:是预编译处理。MyBatis在处理#{}时,会将SQL中的#{}替换为?,然后使用PreparedStatement进行设置参数值。这样可以有效防止SQL注入攻击,因为参数值不会直接拼接到SQL语句中。例如,SELECT * FROM user WHERE username = #{username},MyBatis会将其处理为SELECT * FROM user WHERE username =?,然后通过PreparedStatement设置username的值。
    • **:是字符串替换。MyBatis在处理{}**:是字符串替换。MyBatis在处理{}时,会直接将中的内容替换到SQL语句中。一般用于传入数据库对象,如表名、列名等。例如,SELECTFROM{}中的内容替换到SQL语句中。一般用于传入数据库对象,如表名、列名等。例如,`SELECT * FROM {tableName},如果tableName的值为user,则最终的SQL语句为SELECT * FROM user`。但使用${}时要特别小心,因为如果传入的值来自用户输入,容易导致SQL注入。
  4. 多线程中线程池的核心参数
    • 核心线程数(corePoolSize):线程池中一直存活的线程数,即使这些线程处于空闲状态,也不会被销毁。当有新任务提交时,如果当前线程数小于核心线程数,会创建新的线程来处理任务。
    • 最大线程数(maximumPoolSize):线程池允许创建的最大线程数。当任务队列已满,且当前线程数小于最大线程数时,会创建新的线程来处理任务。
    • 队列容量(workQueue):用于存放等待处理任务的队列。当核心线程数都在忙碌时,新提交的任务会被放入队列中等待处理。常见的队列类型有ArrayBlockingQueue(有界数组队列)、LinkedBlockingQueue(无界链表队列)等。
    • 线程存活时间(keepAliveTime):当线程数大于核心线程数时,多余的空闲线程在等待新任务到来的这段时间内,如果超过了线程存活时间,就会被销毁。
    • 时间单位(unit):线程存活时间的单位,如TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)等。
  5. JUC包下的CountDownLatch和CyclicBarrier的区别
    • CountDownLatch:是一次性的。它允许一个或多个线程等待其他一组线程完成操作。例如,主线程需要等待多个子线程完成数据加载后再进行汇总计算。通过CountDownLatch(int count)构造函数初始化一个计数器,子线程完成任务后调用countDown()方法将计数器减1,主线程调用await()方法等待计数器变为0。计数器一旦变为0,就不能再重置。
    • CyclicBarrier:可以循环使用。它用于让一组线程互相等待,直到所有线程都到达某个屏障点,然后再一起继续执行。例如,多个线程并发处理数据,处理完后需要一起进行下一步的合并操作。通过CyclicBarrier(int parties)构造函数初始化,当调用await()方法的线程数达到parties时,所有线程会被释放继续执行。它的计数器可以重置,通过reset()方法可以将其恢复到初始状态。
  6. Redis有哪些数据类型,分别适用于什么场景
    • String:最基本的数据类型,适用于存储简单的键值对,如缓存用户信息、配置参数等。例如,存储用户的登录状态:SET user:1:status logged_in
    • Hash:适合存储对象,将对象的每个字段作为Hash的一个field,字段值作为Hash的value。例如,存储用户详细信息:HSET user:1 name "John" age 30
    • List:是一个链表结构,适用于存储列表数据,如消息队列、最新消息列表等。可以从列表两端进行插入和删除操作,如LPUSH message_queue "new message"
    • Set:无序且不重复的集合,适用于需要去重的场景,如统计网站的独立访客。可以使用SADD命令添加元素,如SADD unique_visitors "user1"
    • ZSet(Sorted Set):有序且不重复的集合,每个元素都关联一个分数(score),适用于需要根据分数进行排序的场景,如排行榜。例如,存储游戏玩家的积分排行榜:ZADD leaderboard 100 "player1" 200 "player2"
  7. Dubbo的服务调用流程
    • 服务注册:服务提供者启动时,将自己提供的服务注册到注册中心(如Zookeeper)。注册中心保存了服务提供者的地址、端口等信息。
    • 服务订阅:服务消费者启动时,从注册中心订阅自己需要的服务。注册中心会将服务提供者的信息推送给消费者。
    • 服务调用:消费者根据获取到的服务提供者信息,通过网络通信(如Netty)调用服务提供者的接口。在调用过程中,会涉及到负载均衡,Dubbo提供了多种负载均衡策略(如随机、轮询、最少活跃调用数等),根据配置选择合适的策略从多个服务提供者中选择一个进行调用。
    • 监控统计:Dubbo会将服务调用的相关信息(如调用次数、响应时间等)发送到监控中心,用于统计和监控服务的运行情况。
  8. RabbitMQ的工作模式及Direct模式的特点
    • 简单模式(Simple):一个生产者,一个消费者,一条队列,生产者直接将消息发送到队列,消费者从队列中获取消息。
    • 工作队列模式(Work Queue):一个生产者,多个消费者,一条队列。多个消费者竞争从队列中获取消息,每个消息只会被一个消费者处理。
    • 发布订阅模式(Publish/Subscribe):一个生产者,多个消费者,一个交换机(Exchange)和多个队列。生产者将消息发送到交换机,交换机将消息广播到所有绑定的队列,每个队列的消费者都能收到消息。
    • Direct模式:生产者将消息发送到Direct类型的交换机,交换机根据消息的路由键(routing key)将消息发送到对应的队列。只有队列绑定的路由键和消息的路由键完全匹配时,队列才能接收到消息。这种模式适用于需要精确匹配的场景,比如根据订单类型将订单消息发送到不同的队列进行处理。
    • Topic模式:与Direct模式类似,但路由键支持通配符。交换机根据通配符规则将消息发送到匹配的队列。例如,路由键为order.#可以匹配order.createorder.update等以order.开头的所有路由键,适用于更灵活的消息路由场景。
  9. xxl - job的核心原理及分布式任务调度实现
    • 核心原理:xxl - job由调度中心和执行器两部分组成。调度中心负责任务的管理、调度触发等。执行器负责实际任务的执行。调度中心通过数据库存储任务信息、调度日志等。当调度中心触发任务时,会根据任务配置找到对应的执行器,通过网络通信(如HTTP)通知执行器执行任务。
    • 分布式任务调度实现:调度中心可以部署为集群模式,多个调度中心节点可以共同管理任务调度,提高系统的可用性和调度能力。执行器也可以分布式部署,不同的执行器可以处理不同的任务或相同任务的不同分片。通过任务分片机制,调度中心可以将一个大任务拆分成多个小任务,分发给不同的执行器并行处理,从而实现分布式任务调度。例如,处理一个海量数据的计算任务,可以将数据按一定规则分片,每个执行器处理一片数据。