
简介这是一份面向Java求职者与在校生的企业面试真题合集围绕Java基础、高级特性、JVM、Spring框架、数据库、设计模式与算法等高频考点整理帮助读者系统梳理知识脉络、针对性备战不同层级岗位的面试。资源包共895个文件以677张jpg截图、103个txt笔记、73个docx与11个doc文档为主另含少量png、m4a录音、pdf及zip子包压缩后约442.8MB涵盖面经记录、笔试题与专项总结等多种形态。目前已有1727人学习下载内容涉及JavaEE面试宝典、基础常见面试总结及多家企业笔试真题可帮助读者对照真实题目查漏补缺理解面试官考查思路并针对高级职位或移动端方向做专项准备提升面试成功率。1. 面试真题收集这件事为什么你背了三百道还是挂面试真题收集听起来像个体力活实际上是个数据工程问题。我见过太多人把面经复制到记事本里按公司分文件夹结果面试前三天翻不到那道被问到的题。问题不在记忆力在于收集方式从一开始就错了——你收集的是“题目文本”不是“可检索、可分类、可验证的结构化数据”。用 Java 做这件事核心就三件事把散落在各处的面经变成统一格式把题目按技术栈和难度打标把答案和追问链补全。这套东西做出来你面对的不再是几百道孤立的题而是一个能按“JVM 内存模型”“Spring 循环依赖”“MySQL 索引失效”随时调取的本地知识库。适合谁正在准备跳槽的 Java 后端带新人需要题库的 Tech Lead以及想把自己面试经历沉淀成资产的人。下面我从数据采集讲到检索优化每一步都给能跑的代码和参数。2. 从面经文本到结构化题目采集与清洗的完整链路2.1 为什么直接存 Markdown 是死路一条很多人收集面经的第一步是新建一个面试题.md把看到的内容粘进去。三个月后这个文件超过五千行你想找“HashMap 扩容”相关的题CtrlF 只能匹配到字面量如果原文写的是“哈希表 resize”你就漏了。更致命的是你无法回答“我 Spring 相关的题准备到什么程度了”这种统计问题。结构化收集的本质是把非结构化文本拆成字段。一道面试题至少需要这些字段题干、技术栈标签、难度等级、出现频次、答案要点、追问方向、来源公司类型。用 Java 做这件事我一般选 SQLite 做本地存储零配置、单文件、支持全文索引比 JSON 文件靠谱得多。下面这张表是我实际用的字段设计你可以直接抄。字段名类型说明idINTEGER自增主键questionTEXT题干原文保留原始表述stackTEXT技术栈标签逗号分隔如JVM,GCdifficultyINTEGER1-51 为初级5 为专家frequencyINTEGER被问次数初始为 1answerTEXT答案要点支持 MarkdownfollowupsTEXT追问链JSON 数组source_typeTEXT来源类型大厂/中厂/外包/自研created_atTEXT首次录入时间last_reviewTEXT上次复习时间注意stack字段不要用外键关联单独的表面试题的技术栈交叉严重一道题可能同时属于 JVM 和并发用逗号分隔的文本配合 LIKE 查询在几千条量级下完全够用过早规范化只会增加录入负担。2.2 用 Java 写一个面经解析器从复制文本到入库假设你从某个面经帖子里复制了一段文本格式大概是“1. 说说 JVM 内存模型答分为堆、栈、方法区……”这种。手写解析规则不现实因为不同来源格式差异太大。我的做法是先统一成“题干 答案”两段式再用正则提取技术栈标签。import java.sql.*; import java.util.regex.*; public class InterviewParser { // 匹配 数字. 题干答答案 或 数字、题干答案 private static final Pattern QA_PATTERN Pattern.compile( ^\\d[.、]\\s*(.?[?])\\s*(?:答[:]\\s*)?(.)$, Pattern.DOTALL ); // 技术栈关键词映射用于自动打标 private static final String[][] STACK_KEYWORDS { {JVM, 内存|GC|垃圾回收|类加载|字节码|堆|栈}, {并发, 线程|锁|synchronized|volatile|CAS|AQS|线程池}, {Spring, Bean|IOC|AOP|循环依赖|事务|SpringBoot}, {MySQL, 索引|事务隔离|MVCC|锁|explain|慢查询}, {Redis, 缓存|持久化|集群|过期|淘汰|分布式锁}, {消息队列, Kafka|RocketMQ|RabbitMQ|消息|消费|积压}, {分布式, 一致性|CAP|分布式事务|注册中心|限流|熔断}, {网络, TCP|HTTP|三次握手|四次挥手|粘包|IO模型} }; public static void parseAndInsert(String rawText, String sourceType) throws SQLException { String[] lines rawText.split(\\n); try (Connection conn DriverManager.getConnection(jdbc:sqlite:interview.db)) { conn.setAutoCommit(false); String sql INSERT INTO questions(question, stack, difficulty, frequency, answer, source_type, created_at) VALUES(?,?,?,?,?,?,datetime(now)); try (PreparedStatement ps conn.prepareStatement(sql)) { for (String line : lines) { Matcher m QA_PATTERN.matcher(line.trim()); if (!m.find()) continue; String question m.group(1).trim(); String answer m.group(2).trim(); String stack detectStack(question answer); int difficulty estimateDifficulty(question, answer); ps.setString(1, question); ps.setString(2, stack); ps.setInt(3, difficulty); ps.setInt(4, 1); ps.setString(5, answer); ps.setString(6, sourceType); ps.addBatch(); } ps.executeBatch(); conn.commit(); } } } private static String detectStack(String text) { StringBuilder sb new StringBuilder(); for (String[] pair : STACK_KEYWORDS) { if (Pattern.compile(pair[1]).matcher(text).find()) { if (sb.length() 0) sb.append(,); sb.append(pair[0]); } } return sb.length() 0 ? 通用 : sb.toString(); } private static int estimateDifficulty(String q, String a) { // 简单启发题干长度 答案长度 是否含源码/底层关键词 int score 1; if (q.length() 30) score; if (a.length() 200) score; if (q.matches(.*(源码|底层|原理|实现|设计).*)) score; return Math.min(score, 5); } }这段代码的逻辑分三层。第一层是正则匹配QA_PATTERN处理“数字题干答”的常见格式DOTALL让答案可以跨行。第二层是技术栈自动打标STACK_KEYWORDS里每个技术栈对应一组正则命中就追加标签这样你录入时不用手动选分类。第三层是难度估算用题干长度、答案长度和关键词做启发式打分虽然粗糙但比全部默认 3 分强后续复习时可以按难度筛选。参数说明sourceType建议用固定枚举值比如BIG_TECH、MID_TECH、OUTSOURCE、SELF_DEV方便后续按来源统计。frequency初始为 1后续遇到相同题目时用UPDATE questions SET frequency frequency 1 WHERE question ?累加。数据库文件interview.db放在项目根目录记得加.gitignore别把个人题库提交到公开仓库。2.3 去重与合并同一道题被问了八次怎么处理面经收集到一定量后重复题会大量出现。比如“HashMap 底层原理”可能以“说说 HashMap”“HashMap 怎么扩容”“HashMap 线程安全吗”三种表述出现。完全靠字符串相等去重会漏掉变体靠语义相似度又太重。我的做法是两级去重第一级用题干归一化后的哈希第二级用技术栈关键词重叠度。import java.security.MessageDigest; import java.util.*; public class Deduplicator { // 归一化去掉标点、空格、语气词转小写 public static String normalize(String question) { return question.replaceAll([?。,!、\\s], ) .replaceAll((说说|讲讲|谈谈|了解|知道|会吗|是什么|为什么), ) .toLowerCase(); } public static String hash(String normalized) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(normalized.getBytes()); StringBuilder sb new StringBuilder(); for (byte b : digest) sb.append(String.format(%02x, b)); return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } } // 关键词重叠度用于判断两道题是否在问同一件事 public static double overlap(String q1, String q2) { SetString s1 new HashSet(Arrays.asList(q1.split([\\s,]))); SetString s2 new HashSet(Arrays.asList(q2.split([\\s,]))); if (s1.isEmpty() || s2.isEmpty()) return 0; SetString intersection new HashSet(s1); intersection.retainAll(s2); return (double) intersection.size() / Math.min(s1.size(), s2.size()); } }归一化那一步是关键。normalize方法把“说说 HashMap 的底层原理”变成“hashmap底层原理”这样不同语气词的变体就能命中同一个哈希。但光靠哈希不够因为“HashMap 扩容”和“HashMap 原理”归一化后不同实际是包含关系。所以第二级用overlap算关键词重叠度超过 0.6 就认为是同一道题的变体合并时保留题干更完整的那条frequency累加。注意去重不要追求 100% 准确宁可漏合并也不要错合并。把“JVM 内存模型”和“JVM 垃圾回收”合并成一道题比保留两道独立的题危害大得多。我的经验是 overlap 阈值设 0.6 到 0.7 之间低于 0.6 的变体手动确认。3. 给题目打标签与建索引让检索从分钟级降到秒级3.1 技术栈标签体系怎么设计才不混乱自动打标只能解决 70% 的场景剩下 30% 需要手动补。但手动补的前提是标签体系本身不能太细。我见过有人把标签分成“JVM-内存-JMM-可见性”四级结果录入时自己都记不住该选哪个。我的建议是两级标签一级是技术域二级是具体知识点但二级标签不强制填用题干里的关键词自然覆盖。一级标签固定为这些Java基础、JVM、并发、Spring、MySQL、Redis、消息队列、分布式、网络、算法、设计模式、系统设计、通用。每个一级标签下可以有关键词列表用于自动匹配。比如JVM的关键词包括“内存模型、GC、垃圾回收、类加载、字节码、堆、栈、方法区、JMM、happens-before”。这套体系的好处是你复习时可以按域推进不会陷入细节。-- 创建全文索引支持题干和答案的模糊检索 CREATE VIRTUAL TABLE questions_fts USING fts5( question, answer, stack, contentquestions, content_rowidid ); -- 触发器插入时同步到 FTS 表 CREATE TRIGGER questions_ai AFTER INSERT ON questions BEGIN INSERT INTO questions_fts(rowid, question, answer, stack) VALUES (new.id, new.question, new.answer, new.stack); END; -- 查询示例找 JVM 相关且答案里提到“可达性分析”的题 SELECT q.* FROM questions q JOIN questions_fts f ON q.id f.rowid WHERE questions_fts MATCH stack:JVM AND answer:可达性分析 ORDER BY q.frequency DESC;FTS5 是 SQLite 的全文索引模块比 LIKE 快一个数量级。contentquestions表示外部内容表模式不重复存储数据。触发器保证插入时自动同步索引。查询时MATCH支持列过滤和布尔组合stack:JVM限定技术栈answer:可达性分析限定答案内容。按frequency降序排列高频题优先看。3.2 用 Java 封装检索接口按技术栈难度频次组合查询直接写 SQL 查询对使用者不友好封装一个 Java 方法输入技术栈、难度范围、关键词返回排序后的题目列表。import java.sql.*; import java.util.*; public class QuestionRepository { private final Connection conn; public QuestionRepository(String dbPath) throws SQLException { this.conn DriverManager.getConnection(jdbc:sqlite: dbPath); } public ListQuestion search(String stack, int minDiff, int maxDiff, String keyword) throws SQLException { StringBuilder sql new StringBuilder( SELECT q.* FROM questions q JOIN questions_fts f ON q.id f.rowid WHERE 11 ); ListObject params new ArrayList(); if (stack ! null !stack.isEmpty()) { sql.append( AND q.stack LIKE ?); params.add(% stack %); } sql.append( AND q.difficulty BETWEEN ? AND ?); params.add(minDiff); params.add(maxDiff); if (keyword ! null !keyword.isEmpty()) { sql.append( AND questions_fts MATCH ?); params.add(keyword); } sql.append( ORDER BY q.frequency DESC, q.difficulty DESC LIMIT 50); try (PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } ResultSet rs ps.executeQuery(); ListQuestion result new ArrayList(); while (rs.next()) { Question q new Question(); q.id rs.getInt(id); q.question rs.getString(question); q.stack rs.getString(stack); q.difficulty rs.getInt(difficulty); q.frequency rs.getInt(frequency); q.answer rs.getString(answer); result.add(q); } return result; } } }search方法的参数设计对应四种复习场景。只传stack是“我要过一遍 JVM 的题”传minDiff4, maxDiff5是“只看高级题”传keyword是“找和可达性分析相关的题”三个都传就是精确打击。LIMIT 50防止一次返回太多复习时看不过来。ORDER BY frequency DESC让高频题排前面这是最实用的排序策略——被问得多的题你大概率还会遇到。注意stack LIKE %JVM%在数据量超过一万条时会变慢如果题库很大建议把stack字段拆成关联表或者用 FTS5 的stack:JVM语法替代 LIKE。几千条以内 LIKE 完全够用不要过早优化。3.3 答案与追问链的补全策略收集题干只是第一步真正决定面试表现的是答案质量和追问深度。我的做法是每道题录入时答案字段只写“要点骨架”不写完整段落。比如“JVM 内存模型”的答案骨架是“堆、栈、方法区、程序计数器、本地方法栈 各区域作用 可能抛出的异常”。追问链单独存 JSON记录“如果面试官接着问什么我该怎么答”。// 追问链的 JSON 结构示例 String followups [ {q: 堆和栈的区别, a: 堆线程共享栈线程私有堆存对象栈存局部变量和引用}, {q: 方法区在 JDK8 之后有什么变化, a: 永久代被元空间替代元空间使用本地内存}, {q: 什么情况下会 OOM, a: 堆 OOM、栈溢出、元空间 OOM、直接内存 OOM} ] ;这种结构的价值在于你复习时不是背一道题而是背一条追问链。面试官问“JVM 内存模型”你答完基础他大概率会追问“堆和栈的区别”你提前准备好了节奏就在你手里。追问链的 JSON 可以直接存 TEXT 字段查询时用 Java 的 JSON 库解析不需要额外建表。4. 避坑与排查题库建设中最容易翻车的五个点4.1 现象录入时自动打标准确率越来越低原因STACK_KEYWORDS里的正则太宽泛比如“锁”这个关键词同时命中“并发”和“MySQL”导致一道 MySQL 锁的题被打上并发标签。解决给关键词加权重或者用更精确的短语。比如把“锁”改成“synchronized|ReentrantLock|AQS|CAS”用于并发MySQL 的锁用“行锁|表锁|间隙锁|MVCC”。定期抽查自动打标结果发现误标就收紧正则。4.2 现象FTS5 查询报no such column: stack原因FTS5 外部内容表模式下MATCH的列名必须和 FTS 表定义的列一致。如果questions_fts只定义了question, answer, stack但查询时写了stack:JVM而stack列在 FTS 表里存在通常不会报这个错。真正的原因是触发器没建FTS 表里没数据或者contentquestions写成了contentquestions。解决先SELECT count(*) FROM questions_fts确认索引有数据再检查触发器是否生效。4.3 现象去重时把不同难度的题合并了原因overlap只看关键词重叠不看难度。一道“HashMap 原理”难度 2一道“HashMap 扩容死循环”难度 4关键词重叠度高被合并成一道结果复习时漏掉了高难度题。解决去重时增加难度差判断Math.abs(diff1 - diff2) 1就不合并。或者合并时保留两条记录用parent_id关联查询时按难度展开。4.4 现象答案字段越写越长检索变慢原因把完整文章粘进answer字段单条记录超过 10KBFTS5 索引膨胀。解决答案只存要点完整内容存到单独的文件数据库里存文件路径。或者用answer_summary和answer_detail两个字段检索用 summary复习时按需加载 detail。4.5 现象复习时不知道该看哪些题原因没有复习状态跟踪。last_review字段录入了但没用起来。解决加一个review_count字段每次复习后加一查询时按last_review升序排列优先看最久没复习的题。再配合frequency加权高频且久未复习的题排最前。-- 复习优先级排序高频 久未复习 高难度 SELECT *, (frequency * 2 difficulty - julianday(now) julianday(last_review)) AS priority FROM questions WHERE last_review IS NOT NULL ORDER BY priority DESC LIMIT 20;这个排序公式里frequency * 2让高频题权重翻倍difficulty让高难度题靠前julianday差值让久未复习的题优先。last_review为空的新题不参与排序单独用WHERE last_review IS NULL查出来先过一遍。5. 把题库变成面试模拟器一个可验证的进阶技巧题库建好之后最有效的验证方式不是“看一遍”而是“讲一遍”。我给自己写了一个简单的模拟器随机抽 10 道题按技术栈分组要求自己在不看答案的情况下口述然后用录音转文字对比答案要点。这个过程中我发现很多题“看的时候会”“讲的时候卡”。卡住的地方就是真正的薄弱点。具体做法是用 Java 写一个MockInterview类从数据库按技术栈权重抽题。权重规则frequency越高权重越大last_review越久权重越大difficulty在 3 到 4 之间的题权重最高太简单的没必要练太难的面试中很少被问到。public class MockInterview { public static ListQuestion drawQuestions(Connection conn, int count) throws SQLException { String sql SELECT *, (frequency * 1.5 difficulty * 0.8 (julianday(now) - julianday(COALESCE(last_review, 2020-01-01))) * 0.1) AS weight FROM questions WHERE difficulty BETWEEN 2 AND 5 ORDER BY weight DESC LIMIT ? ; // 实际使用时加随机因子避免每次抽同样的题 try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, count * 3); // 先取三倍候选再随机选 ResultSet rs ps.executeQuery(); ListQuestion candidates new ArrayList(); while (rs.next()) { Question q new Question(); q.question rs.getString(question); q.answer rs.getString(answer); q.stack rs.getString(stack); candidates.add(q); } Collections.shuffle(candidates); return candidates.subList(0, Math.min(count, candidates.size())); } } }抽题之后我会用手机录音口述每道题的答案然后回放对照数据库里的答案要点。这个过程很痛苦但效果比反复看强得多。口述时你会发现有些题你只能说出关键词串不成句子有些追问链你只记得第一层第二层就断了。这些就是需要重点补的地方。另一个验证方法是“交叉出题”把 A 技术栈的题用 B 技术栈的视角回答。比如“Redis 持久化”这道题试着从“如果让你设计一个持久化机制”的角度讲而不是背 RDB 和 AOF 的区别。这种练法能暴露你对原理的理解深度面试官稍微换个问法你就不行了。注意模拟器抽题后不要立刻看答案。先口述再对照最后把差异点补进answer字段。这个循环跑上五轮你的答案要点会越来越精炼追问链会越来越完整。我自己的习惯是每周跑一次模拟每次 10 道题录音存到本地一个月后回听第一周的录音能明显感觉到表达流畅度的变化。题库的价值不在于收集了多少题而在于你能否在压力下把每道题讲清楚。这套 Java 实现的收集、清洗、索引、模拟链路从第一行代码到能跑模拟我用了两个周末之后每次跳槽前只需要更新面经和跑模拟不用再从头整理。希望帮到你。本文还有配套的精品资源点击获取