面灵AI→

百度成都 Java 社招一面

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. vibe coding 的使用心得。
  2. 如果 vibe coding 始终无法得到正确结果怎么办?
  3. 百万级数据的展示是如何处理的?LRU 如何在其中应用的?
  4. 单表千万级的数据查询如何优化?
  5. 秒杀场景如何避免超卖(不用 Redis)?
  6. 乐观锁和悲观锁的区别?乐观锁的实际应用。
  7. 举例 Java 源码中用到的 CAS,CAS 的原理。
  8. Netty 为什么 IO 效率高?
  9. 如何定位并优化慢查询?
  10. 手撕:实现一个消息队列。消费端用拉模式,消费端无锁消费,如何保证消息不被重复消费?
  11. 手撕:用 200MB 内存排序硬盘中 2GB 的 int32 数据,用两种解法。

《参考解析》

vibe coding 这类问题怎么答:面试官问它不是想听你夸工具,而是想判断你「有没有工程判断力」。稳妥的回答结构是:我用它做什么(样板代码、单测、文档、陌生的 API 用法、重构时的机械改动),我不用它做什么(核心链路的关键逻辑、涉及并发与一致性的代码、没有测试覆盖就不让它改的东西),以及我怎么验证它(先写测试或用小样例跑通、review diff、看它有没有引入未使用的依赖或吞掉异常)。被追问「始终拿不到正确结果怎么办」时,给出可操作的止损顺序:缩小问题范围、把上下文与约束补全再试一次;换思路——自己写关键部分、用现成库或找官方示例;如果同一个方案失败两次就停下来重构问题描述,而不是继续让它改;最后一定要说清「判断代码对不对的责任始终在我」,这句话是这道题的得分点。

百万级数据展示与 LRU:前端一次渲染百万行必然卡死,所以主线是「分页/虚拟列表 + 缓存 + 索引」。展示层用游标分页或虚拟滚动,只渲染可视区;查询层避免深分页,改成基于索引键的 where id > 上次最大 id limit N;缓存层用 LRU 把最近访问的页或热点实体留在内存,容量有限时淘汰最久未使用的,命中率高、实现简单。LRU 的落地要点:哈希表 + 双向链表做到 O(1) 的查、插、删;Java 里可以直接用 LinkedHashMap 的 accessOrder=true 加 removeEldestEntry 重写,但要讲清它不是线程安全的,高并发下要么加锁分段、要么用 Caffeine 这类带 W-TinyLFU 的库;分布式场景下换成 Redis 并注意内存上限与淘汰策略。再补一句边界:如果数据会被修改,缓存要有一致性策略(更新时删除、过期兜底)。

单表千万级查询怎么优化:分层给答案。SQL 与索引层:EXPLAIN 看执行计划,消灭全表扫描与回表,设计高区分度的联合索引,注意最左前缀、索引列上不要做函数运算、避免隐式类型转换;深分页换游标;把 count(*) 与复杂聚合挪走。结构与存储层:按时间或业务键分区做分区裁剪,冷热分离把历史数据归档,读写分离让报表走从库,大字段拆表或换存储。工程层:加缓存、把非实时统计做成离线宽表、用搜索引擎承接多条件检索。最后一定要说清「先量再拆」:能靠索引和 SQL 解决就别上分库分表,因为它会带来分布式事务、跨片查询、扩容与运维成本;真要分就讲清分片键的选择(避免热点与跨片)、路由方式、以及全局唯一 id 的生成方案。

不用 Redis 的秒杀防超卖与锁的选择:不用缓存等于把所有压力放在数据库,那答案核心就是「原子性 + 削峰 + 幂等」。原子性靠一条带条件的 UPDATE(where stock >= n)或 select ... for update 行锁,也可以用版本号乐观锁(update ... set stock = stock - 1, version = version + 1 where id = ? and version = ?)或库存分段表把热点行拆开;削峰靠消息队列把请求排队、异步落单;幂等靠唯一索引(用户 id + 活动 id)防止同一人重复下单;限流则在前置网关按用户与活动维度做。乐观锁与悲观锁的区别要讲场景:悲观锁先锁后改,冲突多时反而更稳、但要小心锁粒度与死锁;乐观锁不加锁、提交时校验版本,冲突多时重试成本高,适合读多写少。CAS 原理是「比较并交换」的原子指令(cmpxchg),Java 里 AtomicInteger、AtomicReference、ConcurrentHashMap 的 putVal 与 AQS 的状态更新都用它,底层靠 Unsafe 加内存屏障保证可见性与有序性;要顺带说出 ABA 问题与 AtomicStampedReference 的解法,以及高竞争下自旋开销大、LongAdder 用分段累加优化的思路。

Netty 为什么 IO 效率高:从模型到实现串起来讲。IO 模型上它用多路复用(epoll/kqueue),一个或少量线程管理大量连接,避免线程与连接一对一;线程模型是 Reactor 主从多线程,boss 组接连接、worker 组处理读写,每个 EventLoop 绑定一个线程,Channel 的所有事件在同一线程内处理,天然串行化、少加锁。内存上使用池化的 ByteBuf(PooledByteBufAllocator),支持堆外直接内存减少一次拷贝,并用引用计数管理生命周期,避免频繁 GC;还有 FastThreadLocal、轻量对象池等细节优化。协议实现上有可插拔的编解码器链(pipeline + handler),支持拆包粘包处理(LengthFieldBasedFrameDecoder)、写时批量刷新(write + flush)、以及零拷贝的文件传输 FileRegion/sendfile。答题时最后补一句代价与适用面:Netty 的价值在高并发连接与自定义协议的场景,业务逻辑若阻塞 EventLoop 就会拖垮整组连接,所以耗时操作必须丢到业务线程池。

两道手撕题的思路:实现拉模式消息队列,要明确存储结构与并发模型——每个 topic/partition 一个追加写文件或环形数组,写端只追加并更新写偏移,读端各自维护消费位点,用 AtomicLong 或 volatile 偏移量做无锁读(单写多读场景下读取偏移不需要锁,靠 CAS 更新读位点);拉模式的语义是消费者主动按位点取,天然支持回压与批量拉取。不重复消费有两条路:把「读取 + 位点推进」做成幂等的(消费成功才提交位点,失败就重放,业务侧用唯一 id 去重),或者用「至少一次 + 幂等消费」的组合,注意精确一次在单机上也需要事务或幂等表支撑。「200MB 内存排序 2GB int32」有两种经典解法:一是外部归并排序——把文件切成 200MB 以内的块,每块读进内存快排后写回临时文件,再做 k 路归并(用最小堆,缓冲读写);二是分桶/基数排序——按高位字节把数据散到 N 个小文件(每个都能进内存),桶内排序后按桶序拼接输出,天然有序且只需顺序读。回答时把 IO 缓冲、临时文件清理、以及 int32 恰好能用计数排序(4G 计数太大,需按区间分桶)这些细节补上,能明显区分出思考深度。顺带说一句复盘:这场一半时间在挖简历项目,说明社招的主战场是项目深挖而非纯八股,把简历上每个技术点准备到能被追问三层(用了什么、为什么用、边界与替代方案),并把这次没答上的知识在两天内补完重述一遍,比刷更多新题更有效。