Skip to content

AI+Java中级开发面试题库 ​

生成时间:2026-05-20 22:57 | 题目数:10 | AI:Java = 5:5


【面试题】1. RAG系统中,向量检索召回的Top-K结果经常出现语义相关但事实错误的片段,如何优化? ​

【标准答案】这是一个很常见的生产问题。核心思路是"检索-重排-校验"三段式优化:第一,向量检索阶段不要只靠单一Embedding,可以混合关键词检索(BM25)和向量检索做Hybrid Search,互相补充;第二,引入Cross-Encoder重排模型,对Top-K候选做精细化的语义相关性打分,把真正相关的提上来;第三,在生成阶段加一层事实校验,比如让LLM先判断检索内容是否真正能支撑回答,不能的就丢弃。另外从数据侧优化也很关键——切分Chunk时按语义段落切,别按固定字数粗暴切;给每个Chunk加元数据标签(来源、时间、类别),检索时用metadata过滤掉不相关的。生产中这几个手段组合使用,基本能把幻觉率压到可接受范围。

【面试题】2. Java中ConcurrentHashMap在JDK 1.8相比1.7做了哪些核心改进?为什么放弃Segment分段锁? ​

【标准答案】JDK 1.7用的是Segment分段锁,每个Segment继承ReentrantLock,默认16个Segment,并发度就锁死在16。1.8做了三个核心改造:第一,取消了Segment,改用Node数组+CAS+synchronized,锁粒度从Segment级别细化到单个桶(桶头节点),并发度等于数组长度,理论上无限;第二,当链表长度超过8且数组长度≥64时,链表转红黑树,查找从O(n)变成O(logn),高哈希冲突场景性能大幅提升;第三,扩容引入了多线程协助迁移机制(transfer),多个线程可以同时迁移不同桶,扩容不再是单线程瓶颈。放弃Segment的根本原因就是锁粒度太粗,并发度受限,而且Segment本身占内存。1.8的方案在低冲突时CAS无锁操作效率极高,冲突时synchronized锁单个桶,JVM层面对synchronized做了大量优化(偏向锁、轻量级锁),实际性能远超Segment方案。

【面试题】3. 设计一个AI Agent系统,需要支持工具调用(Function Calling)、多轮记忆和任务规划,你会怎么架构? ​

【标准答案】我推荐分层架构:最上层是Planner(任务规划器),把用户目标拆解为可执行的步骤序列;中间是Executor(执行器),每一步判断是调工具还是LLM推理,工具调用走Function Calling协议;最下层是Memory(记忆层),分短期记忆(当前对话上下文)和长期记忆(持久化的向量存储)。关键设计点:第一,Planner用ReAct模式——Reasoning+Action交替,每步先思考再行动,观察结果再决定下一步,而不是一次性规划完所有步骤;第二,工具调用统一走OpenAI Function Calling协议格式,工具定义用JSON Schema描述,Agent根据LLM返回的function_call决定调用哪个工具;第三,记忆管理要设窗口策略,短期记忆用滑动窗口保留最近N轮,长期记忆按语义相关性从向量库召回;第四,加一个Self-Reflection模块,执行完一步后让LLM自检结果是否合理,不合理的回退重试。这套架构用LangChain的Agent+Tool+Memory组件基本能搭出来,Spring AI也支持类似的Function Calling集成。

【面试题】4. Spring Boot中@Autowired注入和构造器注入有什么区别?为什么推荐构造器注入? ​

【标准答案】三种注入方式:字段注入(@Autowired直接加在字段上)、Setter注入、构造器注入。推荐构造器注入,原因有三:第一,不可变性——构造器注入的依赖可以声明为final,对象创建后就不能变了,字段注入做不到;第二,空指针安全——构造器注入保证了依赖在对象创建时就全部就位,不会出现调用时依赖为null的情况,Spring从4.3开始如果只有一个构造器甚至不用写@Autowired;第三,测试友好——单元测试时可以直接new对象传Mock依赖进去,不需要反射或用Spring容器启动。字段注入的问题:依赖隐藏在类内部,从构造函数看不出依赖关系;不能声明final;测试必须用反射注入或启动Spring上下文。实际项目中,如果依赖多构造器参数长,可以用Lombok的@RequiredArgsConstructor自动生成构造器。

【面试题】5. 向量数据库(如Milvus/Pinecone)中,IVF_FLAT、HNSW、IVF_PQ这三种索引各自的适用场景是什么? ​

【标准答案】三种索引的核心区别在精度和资源的取舍:IVF_FLAT是倒排+全量比较,先把向量聚类成N个桶(倒排文件),查询时只搜索最近的几个桶,桶内做暴力计算。优点是精度高、召回有保障,缺点是内存占用大,适合百万级以内、对精度要求高的场景。HNSW是图索引,构建多层导航小世界图,查询时从顶层入口逐层下降找最近邻。优点是查询速度极快(毫秒级),召回率也很高,缺点是构建慢、内存占用最大(要存图结构),适合实时性要求高的在线检索。IVF_PQ是IVF+乘积量化压缩,先聚类再对每个向量做PQ压缩(把高维向量切成子空间分别量化),存储空间压缩8-32倍。优点是内存占用极小,能撑到十亿级,缺点是精度有损,召回率比前两种低。生产选型:百万级以内且精度优先用IVF_FLAT;千万级且要实时用HNSW;亿级且内存有限用IVF_PQ,或者IVF_PQ做初筛再拿IVF_FLAT精排。

【面试题】6. MySQL中InnoDB的MVCC机制是如何实现的?ReadView在RC和RR隔离级别下有什么区别? ​

【标准答案】MVCC的核心是三个组件:隐藏列(trx_id事务ID、roll_pointer回滚指针)、Undo Log版本链、ReadView。每行数据有两个隐藏列,trx_id记录最后修改的事务ID,roll_pointer指向Undo Log中该行的前一个版本,形成版本链。ReadView是事务开始时创建的快照,里面记录了当前活跃事务ID列表(m_ids)、最小活跃事务ID(min_trx_id)和下一个分配的事务ID(max_trx_id)。判断可见性规则:如果版本的trx_id < min_trx_id,说明在ReadView创建前已提交,可见;如果trx_id >= max_trx_id,说明是ReadView创建后的事务,不可见;如果在min和max之间,看是否在m_ids列表里,在就不可见(未提交),不在就可见(已提交)。RC和RR的区别就在于ReadView的创建时机:RC级别下每次SELECT都创建新的ReadView,所以能看到其他事务已提交的最新数据;RR级别下只在第一次SELECT时创建ReadView,后续复用,所以整个事务内看到的数据是一致的快照。这就是为什么RR能解决不可重复读问题。

【面试题】7. 提示词工程中,Chain-of-Thought(CoT)和Few-Shot分别解决什么问题?什么时候需要组合使用? ​

【标准答案】Few-Shot解决的是"格式和任务理解"问题——给模型几个示例,让它知道你期望的输入输出格式是什么样的,适用于任务规则复杂、格式要求严格的场景。CoT解决的是"推理深度"问题——引导模型逐步推理而不是直接跳到结论,适用于需要多步推理、数学计算、逻辑判断的场景。组合使用的典型场景:复杂推理任务且输出格式有严格要求。比如让模型做财务分析,你需要它一步步推理(CoT),同时输出格式必须是特定的JSON结构(Few-Shot给示例)。实践中最有效的方式是Few-Shot+CoT结合——给2-3个示例,每个示例里都展示完整的推理过程,这样模型既学会了格式又学会了推理路径。还有个技巧是Zero-Shot CoT,就是简单加一句"Let's think step by step",在没有示例的情况下也能显著提升推理效果,但精度不如Few-Shot CoT。

【面试题】8. Redis分布式锁用SETNX实现有什么问题?Redlock算法的原理和争议是什么? ​

【标准答案】简单SETNX的问题:第一,锁过期和业务执行时间不匹配——如果业务没执行完锁就过期了,别的线程拿到锁,出现并发问题;第二,客户端崩溃导致锁无法释放——需要加过期时间,但SETNX和EXPIRE不是原子操作(不过现在SET key value NX EX是一条原子命令解决了);第三,集群主从切换——主节点加锁成功还没同步到从节点就挂了,从节点升为主没有锁信息,别的客户端又能加锁。Redlock算法的思路:不依赖单个Redis节点,而是向N个(通常5个)独立Redis实例都去加锁,只有超过半数(≥3个)加锁成功且总耗时没超过锁的过期时间,才算加锁成功。争议主要来自Martin Kleppmann的批评:第一,分布式系统没有可靠时钟,锁过期判断依赖本地时间,时钟漂移会导致问题;第二,进程暂停(GC)可能导致锁过期后还以为自己持锁,继续操作共享资源——建议用fencing token(递增令牌)来保证资源的互斥访问。Redis作者antirez做了反驳但也没完全解决fencing token的问题。生产建议:单Redis实例够用时用SET NX EX + Lua脚本释放锁就行;强一致性要求高的话用Zookeeper或etcd,它们天然支持fencing机制。

【面试题】9. 大模型微调中,LoRA和QLoRA的原理是什么?在什么业务场景下选择微调而非直接用提示词工程? ​

【标准答案】LoRA(Low-Rank Adaptation)的核心思想:微调时不改原始权重矩阵W,而是加一个低秩分解 W' = W + BA,其中B是d×r矩阵,A是r×d矩阵,r远小于d,这样可训练参数量从d²降到2dr,比如d=4096、r=8,参数量从1600万降到6.5万,减少了99.6%。训练时只更新BA,推理时可以把BA合并回W,不增加推理延迟。QLoRA在LoRA基础上做了三件事:第一,把模型权重量化为4-bit NormalFloat(NF4),大幅降低显存;第二,引入双重量化(Double Quantization),对量化常数再量化,再省0.4bit/param;第三,分页优化器(Paged Optimizer),防止梯度检查点时的显存尖峰。QLoRA使得在单张24G显存的4090上微调65B模型成为可能。什么场景该微调:第一,提示词工程调到极限仍达不到效果——比如领域术语理解、特定格式输出;第二,需要模型内化知识而非每次在上下文中提供——比如企业内部知识问答,上下文窗口放不下所有文档;第三,推理成本敏感——微调后小模型可能比每次调大模型+长提示词更经济。不该微调的场景:任务简单提示词就能搞定;数据量太少(<几百条)容易过拟合;需求频繁变化,微调跟不上节奏。

【面试题】10. Kafka如何保证消息的顺序性?分区内的顺序和全局顺序分别怎么实现? ​

【标准答案】Kafka的顺序性保证是分区级别的——同一个分区内,消息严格按照写入顺序消费,这是Kafka原生保证的。所以如果需要顺序消费,核心就是把需要保序的消息路由到同一个分区。具体做法:Producer发送时指定相同的key(比如orderId),Kafka默认用key的hash值决定分区,同一个key的消息一定进同一个分区,消费者按顺序消费就行。全局顺序就是所有消息都要保序,那就只能用一个分区——但这就牺牲了并行度,吞吐量上不去。所以实际生产中几乎不会追求全局顺序,而是按业务维度做局部顺序。需要注意的坑:第一,Producer重试可能导致消息重复或乱序——开启enable.idempotence=true(幂等生产者)可以解决;第二,Consumer消费失败重试,如果异步提交offset可能导致消息被跳过——应该同步提交或用手动提交+幂等消费;第三,分区数变化会导致key路由变化——避免随意增加分区,或者用自定义分区器保证路由稳定。总结:分区有序靠Kafka原生保证,局部有序靠相同key路由同分区,全局有序不推荐(单分区无并行度)。