百度成都 Java 社招一面
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- vibe coding 的使用心得。
- 如果 vibe coding 始终无法得到正确结果怎么办?
- 百万级数据的展示是如何处理的?LRU 如何在其中应用的?
- 单表千万级的数据查询如何优化?
- 秒杀场景如何避免超卖(不用 Redis)?
- 乐观锁和悲观锁的区别?乐观锁的实际应用。
- 举例 Java 源码中用到的 CAS,CAS 的原理。
- Netty 为什么 IO 效率高?
- 如何定位并优化慢查询?
- 手撕:实现一个消息队列。消费端用拉模式,消费端无锁消费,如何保证消息不被重复消费?
- 手撕:用 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 计数太大,需按区间分桶)这些细节补上,能明显区分出思考深度。顺带说一句复盘:这场一半时间在挖简历项目,说明社招的主战场是项目深挖而非纯八股,把简历上每个技术点准备到能被追问三层(用了什么、为什么用、边界与替代方案),并把这次没答上的知识在两天内补完重述一遍,比刷更多新题更有效。