建面試題庫(kù):從面經(jīng)收集到模擬面試全鏈路)
簡(jiǎn)介這是一份面向Java求職者與在校生的企業(yè)面試真題合集圍繞Java基礎(chǔ)、高級(jí)特性、JVM、Spring框架、數(shù)據(jù)庫(kù)、設(shè)計(jì)模式與算法等高頻考點(diǎn)整理幫助讀者系統(tǒng)梳理知識(shí)脈絡(luò)、針對(duì)性備戰(zhàn)不同層級(jí)崗位的面試。資源包共895個(gè)文件以677張jpg截圖、103個(gè)txt筆記、73個(gè)docx與11個(gè)doc文檔為主另含少量png、m4a錄音、pdf及zip子包壓縮后約442.8MB涵蓋面經(jīng)記錄、筆試題與專項(xiàng)總結(jié)等多種形態(tài)。目前已有1727人學(xué)習(xí)下載內(nèi)容涉及JavaEE面試寶典、基礎(chǔ)常見(jiàn)面試總結(jié)及多家企業(yè)筆試真題可幫助讀者對(duì)照真實(shí)題目查漏補(bǔ)缺理解面試官考查思路并針對(duì)高級(jí)職位或移動(dòng)端方向做專項(xiàng)準(zhǔn)備提升面試成功率。1. 面試真題收集這件事為什么你背了三百道還是掛面試真題收集聽(tīng)起來(lái)像個(gè)體力活實(shí)際上是個(gè)數(shù)據(jù)工程問(wèn)題。我見(jiàn)過(guò)太多人把面經(jīng)復(fù)制到記事本里按公司分文件夾結(jié)果面試前三天翻不到那道被問(wèn)到的題。問(wèn)題不在記憶力在于收集方式從一開(kāi)始就錯(cuò)了——你收集的是“題目文本”不是“可檢索、可分類、可驗(yàn)證的結(jié)構(gòu)化數(shù)據(jù)”。用 Java 做這件事核心就三件事把散落在各處的面經(jīng)變成統(tǒng)一格式把題目按技術(shù)棧和難度打標(biāo)把答案和追問(wèn)鏈補(bǔ)全。這套東西做出來(lái)你面對(duì)的不再是幾百道孤立的題而是一個(gè)能按“JVM 內(nèi)存模型”“Spring 循環(huán)依賴”“MySQL 索引失效”隨時(shí)調(diào)取的本地知識(shí)庫(kù)。適合誰(shuí)正在準(zhǔn)備跳槽的 Java 后端帶新人需要題庫(kù)的 Tech Lead以及想把自己面試經(jīng)歷沉淀成資產(chǎn)的人。下面我從數(shù)據(jù)采集講到檢索優(yōu)化每一步都給能跑的代碼和參數(shù)。2. 從面經(jīng)文本到結(jié)構(gòu)化題目采集與清洗的完整鏈路2.1 為什么直接存 Markdown 是死路一條很多人收集面經(jīng)的第一步是新建一個(gè)面試題.md把看到的內(nèi)容粘進(jìn)去。三個(gè)月后這個(gè)文件超過(guò)五千行你想找“HashMap 擴(kuò)容”相關(guān)的題CtrlF 只能匹配到字面量如果原文寫(xiě)的是“哈希表 resize”你就漏了。更致命的是你無(wú)法回答“我 Spring 相關(guān)的題準(zhǔn)備到什么程度了”這種統(tǒng)計(jì)問(wèn)題。結(jié)構(gòu)化收集的本質(zhì)是把非結(jié)構(gòu)化文本拆成字段。一道面試題至少需要這些字段題干、技術(shù)棧標(biāo)簽、難度等級(jí)、出現(xiàn)頻次、答案要點(diǎn)、追問(wèn)方向、來(lái)源公司類型。用 Java 做這件事我一般選 SQLite 做本地存儲(chǔ)零配置、單文件、支持全文索引比 JSON 文件靠譜得多。下面這張表是我實(shí)際用的字段設(shè)計(jì)你可以直接抄。字段名類型說(shuō)明idINTEGER自增主鍵questionTEXT題干原文保留原始表述stackTEXT技術(shù)棧標(biāo)簽逗號(hào)分隔如JVM,GCdifficultyINTEGER1-51 為初級(jí)5 為專家frequencyINTEGER被問(wèn)次數(shù)初始為 1answerTEXT答案要點(diǎn)支持 MarkdownfollowupsTEXT追問(wèn)鏈JSON 數(shù)組source_typeTEXT來(lái)源類型大廠/中廠/外包/自研created_atTEXT首次錄入時(shí)間last_reviewTEXT上次復(fù)習(xí)時(shí)間注意stack字段不要用外鍵關(guān)聯(lián)單獨(dú)的表面試題的技術(shù)棧交叉嚴(yán)重一道題可能同時(shí)屬于 JVM 和并發(fā)用逗號(hào)分隔的文本配合 LIKE 查詢?cè)趲浊l量級(jí)下完全夠用過(guò)早規(guī)范化只會(huì)增加錄入負(fù)擔(dān)。2.2 用 Java 寫(xiě)一個(gè)面經(jīng)解析器從復(fù)制文本到入庫(kù)假設(shè)你從某個(gè)面經(jīng)帖子里復(fù)制了一段文本格式大概是“1. 說(shuō)說(shuō) JVM 內(nèi)存模型答分為堆、棧、方法區(qū)……”這種。手寫(xiě)解析規(guī)則不現(xiàn)實(shí)因?yàn)椴煌瑏?lái)源格式差異太大。我的做法是先統(tǒng)一成“題干 答案”兩段式再用正則提取技術(shù)棧標(biāo)簽。import java.sql.*; import java.util.regex.*; public class InterviewParser { // 匹配 數(shù)字. 題干答答案 或 數(shù)字、題干答案 private static final Pattern QA_PATTERN Pattern.compile( ^\\d[.、]\\s*(.?[?])\\s*(?:答[:]\\s*)?(.)$, Pattern.DOTALL ); // 技術(shù)棧關(guān)鍵詞映射用于自動(dòng)打標(biāo) private static final String[][] STACK_KEYWORDS { {JVM, 內(nèi)存|GC|垃圾回收|類加載|字節(jié)碼|堆|棧}, {并發(fā), 線程|鎖|synchronized|volatile|CAS|AQS|線程池}, {Spring, Bean|IOC|AOP|循環(huán)依賴|事務(wù)|SpringBoot}, {MySQL, 索引|事務(wù)隔離|MVCC|鎖|explain|慢查詢}, {Redis, 緩存|持久化|集群|過(guò)期|淘汰|分布式鎖}, {消息隊(duì)列, Kafka|RocketMQ|RabbitMQ|消息|消費(fèi)|積壓}, {分布式, 一致性|CAP|分布式事務(wù)|注冊(cè)中心|限流|熔斷}, {網(wǎng)絡(luò), 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) { // 簡(jiǎn)單啟發(fā)題干長(zhǎng)度 答案長(zhǎng)度 是否含源碼/底層關(guān)鍵詞 int score 1; if (q.length() 30) score; if (a.length() 200) score; if (q.matches(.*(源碼|底層|原理|實(shí)現(xiàn)|設(shè)計(jì)).*)) score; return Math.min(score, 5); } }這段代碼的邏輯分三層。第一層是正則匹配QA_PATTERN處理“數(shù)字題干答”的常見(jiàn)格式DOTALL讓答案可以跨行。第二層是技術(shù)棧自動(dòng)打標(biāo)STACK_KEYWORDS里每個(gè)技術(shù)棧對(duì)應(yīng)一組正則命中就追加標(biāo)簽這樣你錄入時(shí)不用手動(dòng)選分類。第三層是難度估算用題干長(zhǎng)度、答案長(zhǎng)度和關(guān)鍵詞做啟發(fā)式打分雖然粗糙但比全部默認(rèn) 3 分強(qiáng)后續(xù)復(fù)習(xí)時(shí)可以按難度篩選。參數(shù)說(shuō)明sourceType建議用固定枚舉值比如BIG_TECH、MID_TECH、OUTSOURCE、SELF_DEV方便后續(xù)按來(lái)源統(tǒng)計(jì)。frequency初始為 1后續(xù)遇到相同題目時(shí)用UPDATE questions SET frequency frequency 1 WHERE question ?累加。數(shù)據(jù)庫(kù)文件interview.db放在項(xiàng)目根目錄記得加.gitignore別把個(gè)人題庫(kù)提交到公開(kāi)倉(cāng)庫(kù)。2.3 去重與合并同一道題被問(wèn)了八次怎么處理面經(jīng)收集到一定量后重復(fù)題會(huì)大量出現(xiàn)。比如“HashMap 底層原理”可能以“說(shuō)說(shuō) HashMap”“HashMap 怎么擴(kuò)容”“HashMap 線程安全嗎”三種表述出現(xiàn)。完全靠字符串相等去重會(huì)漏掉變體靠語(yǔ)義相似度又太重。我的做法是兩級(jí)去重第一級(jí)用題干歸一化后的哈希第二級(jí)用技術(shù)棧關(guān)鍵詞重疊度。import java.security.MessageDigest; import java.util.*; public class Deduplicator { // 歸一化去掉標(biāo)點(diǎn)、空格、語(yǔ)氣詞轉(zhuǎn)小寫(xiě) public static String normalize(String question) { return question.replaceAll([?。,!、\\s], ) .replaceAll((說(shuō)說(shuō)|講講|談?wù)剕了解|知道|會(huì)嗎|是什么|為什么), ) .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); } } // 關(guān)鍵詞重疊度用于判斷兩道題是否在問(wèn)同一件事 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()); } }歸一化那一步是關(guān)鍵。normalize方法把“說(shuō)說(shuō) HashMap 的底層原理”變成“hashmap底層原理”這樣不同語(yǔ)氣詞的變體就能命中同一個(gè)哈希。但光靠哈希不夠因?yàn)椤癏ashMap 擴(kuò)容”和“HashMap 原理”歸一化后不同實(shí)際是包含關(guān)系。所以第二級(jí)用overlap算關(guān)鍵詞重疊度超過(guò) 0.6 就認(rèn)為是同一道題的變體合并時(shí)保留題干更完整的那條frequency累加。注意去重不要追求 100% 準(zhǔn)確寧可漏合并也不要錯(cuò)合并。把“JVM 內(nèi)存模型”和“JVM 垃圾回收”合并成一道題比保留兩道獨(dú)立的題危害大得多。我的經(jīng)驗(yàn)是 overlap 閾值設(shè) 0.6 到 0.7 之間低于 0.6 的變體手動(dòng)確認(rèn)。3. 給題目打標(biāo)簽與建索引讓檢索從分鐘級(jí)降到秒級(jí)3.1 技術(shù)棧標(biāo)簽體系怎么設(shè)計(jì)才不混亂自動(dòng)打標(biāo)只能解決 70% 的場(chǎng)景剩下 30% 需要手動(dòng)補(bǔ)。但手動(dòng)補(bǔ)的前提是標(biāo)簽體系本身不能太細(xì)。我見(jiàn)過(guò)有人把標(biāo)簽分成“JVM-內(nèi)存-JMM-可見(jiàn)性”四級(jí)結(jié)果錄入時(shí)自己都記不住該選哪個(gè)。我的建議是兩級(jí)標(biāo)簽一級(jí)是技術(shù)域二級(jí)是具體知識(shí)點(diǎn)但二級(jí)標(biāo)簽不強(qiáng)制填用題干里的關(guān)鍵詞自然覆蓋。一級(jí)標(biāo)簽固定為這些Java基礎(chǔ)、JVM、并發(fā)、Spring、MySQL、Redis、消息隊(duì)列、分布式、網(wǎng)絡(luò)、算法、設(shè)計(jì)模式、系統(tǒng)設(shè)計(jì)、通用。每個(gè)一級(jí)標(biāo)簽下可以有關(guān)鍵詞列表用于自動(dòng)匹配。比如JVM的關(guān)鍵詞包括“內(nèi)存模型、GC、垃圾回收、類加載、字節(jié)碼、堆、棧、方法區(qū)、JMM、happens-before”。這套體系的好處是你復(fù)習(xí)時(shí)可以按域推進(jìn)不會(huì)陷入細(xì)節(jié)。-- 創(chuàng)建全文索引支持題干和答案的模糊檢索 CREATE VIRTUAL TABLE questions_fts USING fts5( question, answer, stack, contentquestions, content_rowidid ); -- 觸發(fā)器插入時(shí)同步到 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 相關(guān)且答案里提到“可達(dá)性分析”的題 SELECT q.* FROM questions q JOIN questions_fts f ON q.id f.rowid WHERE questions_fts MATCH stack:JVM AND answer:可達(dá)性分析 ORDER BY q.frequency DESC;FTS5 是 SQLite 的全文索引模塊比 LIKE 快一個(gè)數(shù)量級(jí)。contentquestions表示外部?jī)?nèi)容表模式不重復(fù)存儲(chǔ)數(shù)據(jù)。觸發(fā)器保證插入時(shí)自動(dòng)同步索引。查詢時(shí)MATCH支持列過(guò)濾和布爾組合stack:JVM限定技術(shù)棧answer:可達(dá)性分析限定答案內(nèi)容。按frequency降序排列高頻題優(yōu)先看。3.2 用 Java 封裝檢索接口按技術(shù)棧難度頻次組合查詢直接寫(xiě) SQL 查詢對(duì)使用者不友好封裝一個(gè) Java 方法輸入技術(shù)棧、難度范圍、關(guān)鍵詞返回排序后的題目列表。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方法的參數(shù)設(shè)計(jì)對(duì)應(yīng)四種復(fù)習(xí)場(chǎng)景。只傳stack是“我要過(guò)一遍 JVM 的題”傳minDiff4, maxDiff5是“只看高級(jí)題”傳keyword是“找和可達(dá)性分析相關(guān)的題”三個(gè)都傳就是精確打擊。LIMIT 50防止一次返回太多復(fù)習(xí)時(shí)看不過(guò)來(lái)。ORDER BY frequency DESC讓高頻題排前面這是最實(shí)用的排序策略——被問(wèn)得多的題你大概率還會(huì)遇到。注意stack LIKE %JVM%在數(shù)據(jù)量超過(guò)一萬(wàn)條時(shí)會(huì)變慢如果題庫(kù)很大建議把stack字段拆成關(guān)聯(lián)表或者用 FTS5 的stack:JVM語(yǔ)法替代 LIKE。幾千條以內(nèi) LIKE 完全夠用不要過(guò)早優(yōu)化。3.3 答案與追問(wèn)鏈的補(bǔ)全策略收集題干只是第一步真正決定面試表現(xiàn)的是答案質(zhì)量和追問(wèn)深度。我的做法是每道題錄入時(shí)答案字段只寫(xiě)“要點(diǎn)骨架”不寫(xiě)完整段落。比如“JVM 內(nèi)存模型”的答案骨架是“堆、棧、方法區(qū)、程序計(jì)數(shù)器、本地方法棧 各區(qū)域作用 可能拋出的異?!?。追問(wèn)鏈單獨(dú)存 JSON記錄“如果面試官接著問(wèn)什么我該怎么答”。// 追問(wèn)鏈的 JSON 結(jié)構(gòu)示例 String followups [ {q: 堆和棧的區(qū)別, a: 堆線程共享?xiàng)>€程私有堆存對(duì)象棧存局部變量和引用}, {q: 方法區(qū)在 JDK8 之后有什么變化, a: 永久代被元空間替代元空間使用本地內(nèi)存}, {q: 什么情況下會(huì) OOM, a: 堆 OOM、棧溢出、元空間 OOM、直接內(nèi)存 OOM} ] ;這種結(jié)構(gòu)的價(jià)值在于你復(fù)習(xí)時(shí)不是背一道題而是背一條追問(wèn)鏈。面試官問(wèn)“JVM 內(nèi)存模型”你答完基礎(chǔ)他大概率會(huì)追問(wèn)“堆和棧的區(qū)別”你提前準(zhǔn)備好了節(jié)奏就在你手里。追問(wèn)鏈的 JSON 可以直接存 TEXT 字段查詢時(shí)用 Java 的 JSON 庫(kù)解析不需要額外建表。4. 避坑與排查題庫(kù)建設(shè)中最容易翻車的五個(gè)點(diǎn)4.1 現(xiàn)象錄入時(shí)自動(dòng)打標(biāo)準(zhǔn)確率越來(lái)越低原因STACK_KEYWORDS里的正則太寬泛比如“鎖”這個(gè)關(guān)鍵詞同時(shí)命中“并發(fā)”和“MySQL”導(dǎo)致一道 MySQL 鎖的題被打上并發(fā)標(biāo)簽。解決給關(guān)鍵詞加權(quán)重或者用更精確的短語(yǔ)。比如把“鎖”改成“synchronized|ReentrantLock|AQS|CAS”用于并發(fā)MySQL 的鎖用“行鎖|表鎖|間隙鎖|MVCC”。定期抽查自動(dòng)打標(biāo)結(jié)果發(fā)現(xiàn)誤標(biāo)就收緊正則。4.2 現(xiàn)象FTS5 查詢報(bào)no such column: stack原因FTS5 外部?jī)?nèi)容表模式下MATCH的列名必須和 FTS 表定義的列一致。如果questions_fts只定義了question, answer, stack但查詢時(shí)寫(xiě)了stack:JVM而stack列在 FTS 表里存在通常不會(huì)報(bào)這個(gè)錯(cuò)。真正的原因是觸發(fā)器沒(méi)建FTS 表里沒(méi)數(shù)據(jù)或者contentquestions寫(xiě)成了contentquestions。解決先SELECT count(*) FROM questions_fts確認(rèn)索引有數(shù)據(jù)再檢查觸發(fā)器是否生效。4.3 現(xiàn)象去重時(shí)把不同難度的題合并了原因overlap只看關(guān)鍵詞重疊不看難度。一道“HashMap 原理”難度 2一道“HashMap 擴(kuò)容死循環(huán)”難度 4關(guān)鍵詞重疊度高被合并成一道結(jié)果復(fù)習(xí)時(shí)漏掉了高難度題。解決去重時(shí)增加難度差判斷Math.abs(diff1 - diff2) 1就不合并?;蛘吆喜r(shí)保留兩條記錄用parent_id關(guān)聯(lián)查詢時(shí)按難度展開(kāi)。4.4 現(xiàn)象答案字段越寫(xiě)越長(zhǎng)檢索變慢原因把完整文章粘進(jìn)answer字段單條記錄超過(guò) 10KBFTS5 索引膨脹。解決答案只存要點(diǎn)完整內(nèi)容存到單獨(dú)的文件數(shù)據(jù)庫(kù)里存文件路徑?;蛘哂胊nswer_summary和answer_detail兩個(gè)字段檢索用 summary復(fù)習(xí)時(shí)按需加載 detail。4.5 現(xiàn)象復(fù)習(xí)時(shí)不知道該看哪些題原因沒(méi)有復(fù)習(xí)狀態(tài)跟蹤。last_review字段錄入了但沒(méi)用起來(lái)。解決加一個(gè)review_count字段每次復(fù)習(xí)后加一查詢時(shí)按last_review升序排列優(yōu)先看最久沒(méi)復(fù)習(xí)的題。再配合frequency加權(quán)高頻且久未復(fù)習(xí)的題排最前。-- 復(fù)習(xí)優(yōu)先級(jí)排序高頻 久未復(fù)習(xí) 高難度 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;這個(gè)排序公式里frequency * 2讓高頻題權(quán)重翻倍difficulty讓高難度題靠前julianday差值讓久未復(fù)習(xí)的題優(yōu)先。last_review為空的新題不參與排序單獨(dú)用WHERE last_review IS NULL查出來(lái)先過(guò)一遍。5. 把題庫(kù)變成面試模擬器一個(gè)可驗(yàn)證的進(jìn)階技巧題庫(kù)建好之后最有效的驗(yàn)證方式不是“看一遍”而是“講一遍”。我給自己寫(xiě)了一個(gè)簡(jiǎn)單的模擬器隨機(jī)抽 10 道題按技術(shù)棧分組要求自己在不看答案的情況下口述然后用錄音轉(zhuǎn)文字對(duì)比答案要點(diǎn)。這個(gè)過(guò)程中我發(fā)現(xiàn)很多題“看的時(shí)候會(huì)”“講的時(shí)候卡”??ㄗ〉牡胤骄褪钦嬲谋∪觞c(diǎn)。具體做法是用 Java 寫(xiě)一個(gè)MockInterview類從數(shù)據(jù)庫(kù)按技術(shù)棧權(quán)重抽題。權(quán)重規(guī)則frequency越高權(quán)重越大last_review越久權(quán)重越大difficulty在 3 到 4 之間的題權(quán)重最高太簡(jiǎn)單的沒(méi)必要練太難的面試中很少被問(wèn)到。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 ? ; // 實(shí)際使用時(shí)加隨機(jī)因子避免每次抽同樣的題 try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, count * 3); // 先取三倍候選再隨機(jī)選 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())); } } }抽題之后我會(huì)用手機(jī)錄音口述每道題的答案然后回放對(duì)照數(shù)據(jù)庫(kù)里的答案要點(diǎn)。這個(gè)過(guò)程很痛苦但效果比反復(fù)看強(qiáng)得多。口述時(shí)你會(huì)發(fā)現(xiàn)有些題你只能說(shuō)出關(guān)鍵詞串不成句子有些追問(wèn)鏈你只記得第一層第二層就斷了。這些就是需要重點(diǎn)補(bǔ)的地方。另一個(gè)驗(yàn)證方法是“交叉出題”把 A 技術(shù)棧的題用 B 技術(shù)棧的視角回答。比如“Redis 持久化”這道題試著從“如果讓你設(shè)計(jì)一個(gè)持久化機(jī)制”的角度講而不是背 RDB 和 AOF 的區(qū)別。這種練法能暴露你對(duì)原理的理解深度面試官稍微換個(gè)問(wèn)法你就不行了。注意模擬器抽題后不要立刻看答案。先口述再對(duì)照最后把差異點(diǎn)補(bǔ)進(jìn)answer字段。這個(gè)循環(huán)跑上五輪你的答案要點(diǎn)會(huì)越來(lái)越精煉追問(wèn)鏈會(huì)越來(lái)越完整。我自己的習(xí)慣是每周跑一次模擬每次 10 道題錄音存到本地一個(gè)月后回聽(tīng)第一周的錄音能明顯感覺(jué)到表達(dá)流暢度的變化。題庫(kù)的價(jià)值不在于收集了多少題而在于你能否在壓力下把每道題講清楚。這套 Java 實(shí)現(xiàn)的收集、清洗、索引、模擬鏈路從第一行代碼到能跑模擬我用了兩個(gè)周末之后每次跳槽前只需要更新面經(jīng)和跑模擬不用再?gòu)念^整理。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取