写在前面

去年七月,我参加了滴滴校招提前批后端开发的面试,部门是网约车安全,一面在一天内完成。

整场面试涵盖了项目经验、Java并发基础、分布式中间件到算法手撕,考察范围极广,节奏也非常紧凑。

在这里,我把所有面试题目和思路整理出来,帮助正在备战大厂的你少走弯路。

⚡ 友情提示:本文干货密集,建议收藏后反复阅读,配合实际刷题效果更佳!

面试全貌一览

本次一面共涉及 16 道题目,分布在以下几个核心方向:

  1. 自我介绍 & 项目深挖(2题)
  2. 分库分表设计(4题)
  3. Java 集合 & 并发(6题)
  4. Kafka 高性能 & 延迟队列(2题)
  5. 算法手撕:合并区间(1题)

Part 1:项目深挖 — 分库分表设计

一上来就是项目经验,但别以为聊聊就行——面试官会顺着你的项目,把数据库设计问到底。

Q1. 什么时候进行分表?按照什么维度?

当单表数据量达到瓶颈(通常建议 500w 行以内)或读写性能出现瓶颈时,就需要考虑分表。

分表维度通常有两种:水平分表(按行拆分,如按用户ID、时间范围)和垂直分表(按列拆分,将热点字段与冷数据分离)。

具体选择哪种维度,要结合业务访问模式来决定——如果查询大多数是按用户维度,就以用户ID取模做水平分片。

Q2. 为什么一张表的上限要设计为 500w?

500w 并不是一个"官方"定论,而是工程经验值。MySQL InnoDB 的 B+ 树索引在数据量超过一定规模后,树高度增加,磁盘 I/O 次数增多,查询性能下降明显。

通常一个 B+ 树节点大小为 16KB,3层树高可以支撑约 2000w 行。但考虑到索引维护、并发写入和备份压力,500w ~ 1000w 是更稳妥的工程选择。

备战Tips:面试时说出「树高度与 I/O 次数」的关系,加分项!

Q3. 可以基于时间查询任务状态吗?如何根据其他字段定位到表号?

如果分表是按用户ID取模,那么按时间查询会成为一个难题,因为无法直接定位到哪张表。

常见解法:① 建立映射索引表,记录时间与表号的映射关系;② 采用全局二级索引(类似ES);③ 范围查询时广播查所有分表再聚合(scatter-gather);④ 设计时兼顾查询维度,如采用分区键 + 辅助键的组合分片策略。

这道题的核心考察点:分片键选择与查询灵活性之间的权衡取舍。

Part 2:Java 集合 & 并发

这一part是重头戏,滴滴对底层原理的考察非常深入,不只是问「会不会用」,而是要你说清楚「为什么」。

Q4. HashMap 底层实现?

JDK 8 之后,HashMap 采用 数组 + 链表 + 红黑树 的结构。默认初始容量 16,负载因子 0.75。

当链表长度超过 8 且数组长度 >= 64 时,链表转换为红黑树,将查询时间复杂度从 O(n) 降为 O(log n)。

Q5. 如何解决哈希冲突?哈希函数有什么优化?

HashMap 使用链地址法解决冲突。在哈希函数上,JDK 8 引入了扰动函数:将 hashCode 的高16位与低16位进行异或,使哈希值更加均匀分布,降低碰撞概率。

Q6. 为什么 HashMap 底层数组长度是 2 的 n 次幂?

核心原因是为了用位运算 (n-1) & hash 替代取模运算 hash % n,效率更高。当数组长度是 2 的 n 次幂时,(n-1) 的二进制全为 1,&运算等价于取余,同时保证了索引均匀分布。

扩容时同样受益:元素要么留在原位,要么移动到「原位置 + 旧容量」处,无需重新计算所有哈希值。

Q7. 介绍 ConcurrentHashMap?

JDK 8 中 ConcurrentHashMap 放弃了 JDK 7 的 Segment 分段锁,改用 CAS + synchronized 的方式:

• 对空桶插入时使用 CAS 无锁操作;对已有节点的操作则锁住链表头节点(粒度更细);

• 扩容支持多线程协同迁移(transfer),大幅提升并发性能。

Q8. CAS 是什么?ABA 问题如何解决?

CAS(Compare And Swap)是一种乐观锁机制,包含三个操作数:内存值、预期值、新值。只有当内存值等于预期值时,才将其更新为新值,否则重试。

ABA 问题:变量从 A 变为 B 再变回 A,CAS 无法感知中间的变化。

解决方案:使用 AtomicStampedReference,在值的基础上附加版本号(stamp),每次更新时版本号自增,从而识别 ABA 变化。

备战Tips:除了版本号,也可以用 AtomicMarkableReference(布尔标记),适用于只需标记「是否被修改过」的场景。

Q9. 介绍 AQS,是公平锁还是非公平锁?公平锁如何实现?

AQS(AbstractQueuedSynchronizer)是 JUC 中锁和同步器的核心框架,底层维护了一个 volatile int state 和一个 CLH 双向等待队列。

ReentrantLock 默认是非公平锁(直接 CAS 抢锁),公平锁版本会先检查队列中是否有等待线程,有则入队等待,保证 FIFO 顺序。

Q10. 讲一下条件锁(条件队列)?

Condition 是 AQS 提供的条件变量,类比 Object.wait/notify,但功能更强(支持多个条件队列)。

调用 condition.await() 时,当前线程释放锁并进入条件队列;调用 condition.signal() 时,将条件队列中的线程转移到 AQS 等待队列,重新竞争锁。

典型应用:生产者-消费者模型,ReentrantLock + 两个 Condition 分别表示「队列非空」和「队列未满」。

Part 3:Kafka 高性能揭秘

Q11. Kafka 为什么是高性能的?

Kafka 的高性能来自多个维度的协同设计:

① 顺序写磁盘:消息追加写入 Segment 文件,比随机写快几十倍;

② 零拷贝(Zero Copy):使用 sendfile 系统调用,跳过用户空间,数据直接从 PageCache 发送到 NIC;

③ 批量压缩:Producer 端批量发送 + 压缩,减少网络传输量;

④ 分区并行:多 Partition 支持并行消费,水平扩展吞吐;

⑤ PageCache:利用操作系统的 Page Cache 做缓冲,减少实际磁盘 I/O。

Q12. Kafka 如何实现延迟队列?

Kafka 原生并不支持延迟队列,但可以通过以下方案实现:

方案一(时间轮):Kafka 内部使用多层时间轮(Hierarchical Timing Wheels)处理延迟任务,可借鉴此思路在业务层实现。

方案二(层级Topic):设计多个延迟级别的 Topic(如 delay-5s、delay-10s、delay-30s),消息先投递到对应延迟 Topic,由专属 Consumer 轮询到期后再投递到真正的业务 Topic。

方案三(结合 RocketMQ):如果延迟队列需求较强,可考虑换用原生支持 18 级延迟消息的 RocketMQ。

备战Tips:面试中如果你能主动对比 Kafka 与 RocketMQ 的延迟队列方案,会显得非常有深度。

Part 4:算法手撕 — 合并区间

LeetCode 56. Merge Intervals,难度中等,属于高频必刷题。

核心思路:

① 将所有区间按照左端点升序排序;

② 遍历区间,维护一个「当前合并区间」;

③ 若下一区间的左端点 <= 当前区间右端点,则合并(右端点取 max);

④ 否则将当前区间加入结果,并以新区间开始新一轮合并。

⏱ 时间复杂度 O(n log n),空间复杂度 O(n)。务必处理好边界条件(空输入、单个区间等)。

int[][] merge(int[][] intervals) {

    Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

    List<int[]> res = new ArrayList<>();

    for (int[] cur : intervals) {

        if (res.isEmpty() || res.getLast()[1] < cur[0])

            res.add(cur);

        else

            res.getLast()[1] = Math.max(res.getLast()[1], cur[1]);

    }

    return res.toArray(new int[0][]);

}

总结与复盘

考察方向

涉及题目

重要程度

项目 & 数据库

分库分表、分片策略、索引设计

⭐⭐⭐⭐⭐

Java 集合

HashMap、ConcurrentHashMap

⭐⭐⭐⭐⭐

Java 并发

CAS/ABA、AQS、条件锁

⭐⭐⭐⭐⭐

消息队列

Kafka 高性能、延迟队列

⭐⭐⭐⭐

算法

合并区间(LeetCode 56)

⭐⭐⭐

✅ 整体来看,滴滴一面的考察非常扎实,重点在于底层原理的理解和工程实践经验的结合。

如果你也在备战大厂,建议把 Java 并发(JUC)和 MySQL 索引 & 分表策略作为重点攻坚方向。

如果这篇文章对你有帮助,欢迎点赞、在看、分享,你的支持是我持续分享的动力!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐