與備考突破)
每年校招季Java研發(fā)崗的筆試總是最先砸過來的那塊石頭。我在后端開發(fā)這條路上待了十多年每年都會(huì)抽時(shí)間翻一翻各家公司的校招筆試卷——倒不是為了出題而是這些卷子本身就是一份很誠實(shí)的行業(yè)技術(shù)地圖能清楚告訴你這個(gè)崗位真正需要什么樣的人你離要求還差多遠(yuǎn)。百度2023校招Java研發(fā)工程師筆試卷第一批就是其中很有代表性的一套。這套卷子覆蓋了Java基礎(chǔ)、集合框架、JVM、并發(fā)編程、算法與數(shù)據(jù)結(jié)構(gòu)、Spring和MySQL等后端核心知識(shí)考察的深度和方向基本代表了頭部互聯(lián)網(wǎng)公司對(duì)校招Java工程師的通用水準(zhǔn)。無論你是明年準(zhǔn)備投遞校招的在校生還是剛轉(zhuǎn)行Java想系統(tǒng)自檢的開發(fā)者把這份卷子的出題邏輯和考點(diǎn)吃透都能少走很多彎路。下面我按試卷常見的章節(jié)結(jié)構(gòu)結(jié)合我自己在實(shí)際開發(fā)中踩過的坑把這套題背后的核心知識(shí)點(diǎn)逐個(gè)拆開講。不看答案先看它為什么這么考。1. 試卷結(jié)構(gòu)與出題邏輯這套題到底在篩選什么人1.1 五大模塊的考察重點(diǎn)與分值分布一份典型的Java研發(fā)工程師筆試卷通常不會(huì)只考純語法而是圍繞后端工程師日常工作的核心技能樹來設(shè)計(jì)。我拆過不少大廠的卷子百度這套的模塊劃分比較有代表性大致如下考察模塊常見題型核心考察內(nèi)容大致占比Java基礎(chǔ)與集合框架選擇題、填空題面向?qū)ο?、String、HashMap、ArrayList、異常體系25%JVM與內(nèi)存管理選擇題、簡答題內(nèi)存分區(qū)、垃圾回收、類加載、OOM排查15%并發(fā)與多線程選擇題、代碼分析synchronized、volatile、線程池、鎖機(jī)制20%算法與數(shù)據(jù)結(jié)構(gòu)編程題排序、鏈表、二叉樹、動(dòng)態(tài)規(guī)劃、邊界處理25%框架與工程化選擇題、綜合題Spring IoC/AOP、MySQL索引與事務(wù)、設(shè)計(jì)模式15%這個(gè)比例不是隨便定的。算法占比高是因?yàn)樾U泻蜻x人沒有太多項(xiàng)目經(jīng)驗(yàn)可以考察算法題是衡量邏輯思維和代碼功底最公平的尺子Java基礎(chǔ)占比高是因?yàn)楹蠖碎_發(fā)的所有框架和中間件都構(gòu)建在這些基礎(chǔ)之上基礎(chǔ)不牢后面全是空中樓閣。1.2 出題人真正想看到的三種能力我參與過幾次校招面試也旁聽過筆試閱卷。說實(shí)話筆試分?jǐn)?shù)高的人不少但真正能通過面試的人往往在筆試中就展現(xiàn)出一些共性特質(zhì)。第一個(gè)是知識(shí)體系的完整性。Java的知識(shí)點(diǎn)就像一張網(wǎng)從語法到JVM從單線程到并發(fā)從集合到框架彼此都有聯(lián)系。很多候選人能背出HashMap的原理但一問到“為什么JDK 1.8要用紅黑樹而不是一直用鏈表”就卡殼。這種割裂式的記憶筆試選擇題能蒙對(duì)但稍一變形就露餡。第二個(gè)是邊界意識(shí)。筆試編程題最常考的其實(shí)不是復(fù)雜算法而是你對(duì)邊界條件的敏感度。數(shù)組為空、數(shù)組長度為1、數(shù)據(jù)量極大、輸入含重復(fù)值——這些邊界情況才是拉開分差的地方。我在下文的算法章節(jié)里會(huì)專門展開。第三個(gè)是工程思維。同樣是寫一個(gè)線程池有人能寫出參數(shù)、拒絕策略、隊(duì)列選擇的完整方案有人只會(huì)寫Executors.newFixedThreadPool()。前者知道每個(gè)參數(shù)在什么場景下會(huì)出問題后者只是“用過”。這套卷子的綜合題部分就是在篩選這兩種人。2. Java核心基礎(chǔ)選擇題里最容易丟分的細(xì)節(jié)2.1 HashMap的存儲(chǔ)邏輯與并發(fā)問題HashMap幾乎是Java筆試必考中的必考沒有之一。它考的不是你能不能寫出put方法而是你能不能講清楚一個(gè)鍵值對(duì)從進(jìn)門到落戶的完整過程。put一個(gè)鍵值對(duì)時(shí)HashMap先對(duì)key做hash()擾動(dòng)再用(n-1) hash算出桶下標(biāo)。如果該位置為空直接放入如果不為空就遍歷這個(gè)桶里的鏈表或紅黑樹通過equals判斷是否有相同key有則覆蓋舊值沒有則追加到鏈表尾部。當(dāng)鏈表長度達(dá)到8且數(shù)組長度大于等于64時(shí)鏈表會(huì)轉(zhuǎn)成紅黑樹。容量不足時(shí)觸發(fā)resize()數(shù)組長度翻倍所有元素重新計(jì)算桶位置。這段流程里有三個(gè)細(xì)節(jié)是出題人最愛挖坑的地方。第一為什么取模要用(n-1) hash而不是hash % n因?yàn)镠ashMap的數(shù)組長度始終是2的冪(n-1)的二進(jìn)制全是低位1做位運(yùn)算比取模效率高。如果你自己設(shè)計(jì)一個(gè)容器類也想用這種優(yōu)化就必須保證容量是2的冪。第二JDK 1.7和1.8的差異。1.7的頭插法在并發(fā)擴(kuò)容時(shí)會(huì)形成環(huán)形鏈表導(dǎo)致get死循環(huán)這是一個(gè)經(jīng)典的并發(fā)事故1.8改成尾插法規(guī)避了這個(gè)問題但并發(fā)下仍有數(shù)據(jù)覆蓋丟失的風(fēng)險(xiǎn)。所以面試官常會(huì)追問一句“并發(fā)場景下你還會(huì)用HashMap嗎”標(biāo)準(zhǔn)答案是用ConcurrentHashMap但你要能說出原因1.8的ConcurrentHashMap放棄了1.7的分段鎖改用CAS加synchronized鎖住每個(gè)桶的頭節(jié)點(diǎn)并發(fā)度更高鎖粒度更細(xì)。第三紅黑樹不是隨便轉(zhuǎn)的。鏈表長度達(dá)到8時(shí)先看數(shù)組長度如果小于64優(yōu)先擴(kuò)容而不是轉(zhuǎn)樹。因?yàn)閿U(kuò)容后鏈表會(huì)被拆分長度自然下降。這個(gè)條件寫死在代碼里很多人背了“8”卻忽略了“64”。2.2 JVM內(nèi)存模型與OutOfMemoryError的排查思路熱詞里有“java: outofmemoryerror: insufficient memory”這確實(shí)是筆試和面試都愛聊的話題。JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)分為堆、虛擬機(jī)棧、本地方法棧、方法區(qū)元空間和程序計(jì)數(shù)器。筆試??嫉氖歉闱宄糠NOOM對(duì)應(yīng)哪個(gè)區(qū)域。堆內(nèi)存不足會(huì)報(bào)java.lang.OutOfMemoryError: Java heap space最常見的原因是對(duì)象太多或有大對(duì)象長期存活比如一次性把幾百萬行數(shù)據(jù)載入內(nèi)存。棧溢出則是StackOverflowError多見于遞歸沒有正確設(shè)置終止條件。元空間溢出會(huì)報(bào)Metaspace通常是動(dòng)態(tài)生成類過多比如反射或CGLIB代理使用不當(dāng)。真正工作以后排查OOM不能靠猜。我自己的標(biāo)準(zhǔn)流程是先用jps找到Java進(jìn)程再用jmap -heap pid看堆內(nèi)存使用情況如果堆快照沒有明顯異常就用jmap -dump:formatb,fileheap.hprof pid導(dǎo)出堆轉(zhuǎn)儲(chǔ)文件用MAT或VisualVM分析。有個(gè)小技巧是啟動(dòng)參數(shù)里加上-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/路徑讓JVM在OOM時(shí)自動(dòng)保存現(xiàn)場。筆試不會(huì)讓你真的去線上排查但可能會(huì)給你一段代碼問你“這段程序運(yùn)行后會(huì)不會(huì)OOM發(fā)生在哪個(gè)區(qū)域”。這類題的答題思路是先看對(duì)象存在哪里、生命周期多長再看是否存在無限循環(huán)或遞歸最后判斷是堆、棧還是元空間的問題。比如public class OomDemo { public static void main(String[] args) { Listbyte[] list new ArrayList(); while (true) { list.add(new byte[1024 * 1024]); } } }這段代碼會(huì)不斷向堆里塞1MB的字節(jié)數(shù)組list一直強(qiáng)引用這些對(duì)象GC無法回收最終拋出堆OOM。這個(gè)例子沒有技術(shù)含量但它是理解“什么情況下對(duì)象無法被回收”的最小模型比死記硬背參數(shù)有用得多。2.3 String、運(yùn)算符與異常那些“看起來簡單”的題筆試的選擇題部分最喜歡從一些不起眼的小知識(shí)點(diǎn)下手。比如String的和equals比如i和i比如switch能否傳null。這些題單獨(dú)的任何一個(gè)大部分人都能答對(duì)但放在同一張卷子里連續(xù)出現(xiàn)時(shí)就是考驗(yàn)臨場心態(tài)和知識(shí)熟練度了。String相關(guān)的坑集中在常量池。直接量賦值會(huì)走常量池new String()會(huì)創(chuàng)建堆對(duì)象intern()方法可以把字符串手動(dòng)放入常量池。而String是final的每次修改都會(huì)生成新對(duì)象所以字符串拼接在循環(huán)里應(yīng)該用StringBuilder。運(yùn)算符相關(guān)的經(jīng)典題是int i 0; i i; System.out.println(i); // 輸出多少答案是0。因?yàn)閕先把舊值0壓入操作數(shù)棧再對(duì)局部變量表的i加1最后賦值時(shí)用的是操作數(shù)棧里的舊值0。這個(gè)題看似刁鉆實(shí)際考察的是JVM字節(jié)碼層面的執(zhí)行順序理解了這個(gè)模型Java里很多詭異行為都能解釋通。異常體系也是高頻考點(diǎn)Error不需要捕獲Exception分為受檢異常和非受檢異常RuntimeException及其子類是非受檢。出題人常給一段try-catch-finally代碼問finally塊里寫return會(huì)怎樣。結(jié)論是finally中的return會(huì)覆蓋try或catch中的return同時(shí)也會(huì)吞掉異常屬于壞代碼看到可以直接判斷這是錯(cuò)誤答案。3. 并發(fā)與多線程拉開分差的核心區(qū)塊3.1 synchronized與ReentrantLock的底層差異并發(fā)題目在校招筆試?yán)锏牡匚辉絹碓礁?。頭部的后端系統(tǒng)沒有一個(gè)能繞開多線程問題。synchronized和ReentrantLock是兩種最基礎(chǔ)的鎖但內(nèi)核完全不同。synchronized在JDK 1.6之后引入了鎖升級(jí)機(jī)制偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。偏向鎖用于“只有一個(gè)線程訪問”的場景記錄線程ID省去CAS開銷一旦出現(xiàn)競爭升級(jí)為輕量級(jí)鎖通過CAS自旋獲取鎖自旋超過閾值或競爭加劇升級(jí)為重量級(jí)鎖由操作系統(tǒng)互斥量實(shí)現(xiàn)線程會(huì)進(jìn)入阻塞狀態(tài)。這個(gè)設(shè)計(jì)思路是大部分鎖在真實(shí)場景下幾乎沒有競爭沒必要一上來就動(dòng)用昂貴的系統(tǒng)調(diào)用。ReentrantLock則基于AQSAbstractQueuedSynchronizer實(shí)現(xiàn)。它有公平鎖和非公平鎖兩種模式非公平鎖在lock時(shí)先嘗試一次CAS插隊(duì)成功就直接拿到鎖失敗才進(jìn)入等待隊(duì)列公平鎖則嚴(yán)格按FIFO順序。筆試愛考“非公平鎖會(huì)不會(huì)導(dǎo)致線程饑餓”——理論上會(huì)但實(shí)踐中非公平鎖吞吐量更高因?yàn)闇p少了線程喚醒的開銷。還有一個(gè)??键c(diǎn)是可重入性。兩者都是可重入鎖意思是同一個(gè)線程可以多次獲取同一把鎖而不會(huì)死鎖。synchronized由JVM隱式支持ReentrantLock通過AQS的state計(jì)數(shù)實(shí)現(xiàn)。如果讓你設(shè)計(jì)一個(gè)鎖你會(huì)怎么記錄“當(dāng)前持有者線程”和“重入次數(shù)”AQS的做法是用一個(gè)volatile的state字段配合線程記錄這道面試題就是在考你對(duì)AQS核心模型的理解。3.2 volatile的可見性與指令重排volatile是并發(fā)選擇題里的???。它有兩個(gè)語義保證變量在不同線程間的可見性禁止指令重排序。但它不保證原子性??梢娦栽砩婕癑ava內(nèi)存模型JMM。JMM規(guī)定每個(gè)線程有獨(dú)立的工作內(nèi)存變量操作先在工作內(nèi)存中進(jìn)行再同步回主內(nèi)存。如果一個(gè)變量沒有同步機(jī)制線程A修改后線程B可能讀不到最新值。volatile變量在寫操作時(shí)會(huì)強(qiáng)制刷新到主內(nèi)存讀操作時(shí)會(huì)強(qiáng)制從主內(nèi)存加載同時(shí)通過內(nèi)存屏障阻止重排序。經(jīng)典的反例是private static volatile int count 0; public static void increment() { count; // 不是原子操作 }count包括讀、加、寫三步多線程并發(fā)執(zhí)行時(shí)即使count是volatile也會(huì)出現(xiàn)丟更新。解決方式是使用AtomicInteger的getAndIncrement()它基于CAS循環(huán)。這里有個(gè)筆試答題技巧凡是涉及“并發(fā)下計(jì)數(shù)、累加”的題目優(yōu)先想到原子類或鎖不要選volatile。凡是涉及“標(biāo)志位、開關(guān)量、單例雙重檢查中的實(shí)例引用”的題目優(yōu)先想到volatile。比如DCL單例private static volatile Singleton instance;instance必須加volatile否則在instance new Singleton()的三步分配內(nèi)存、初始化對(duì)象、賦值引用中指令重排可能導(dǎo)致另一個(gè)線程拿到未初始化完成的對(duì)象引用。3.3 線程池參數(shù)與拒絕策略從使用到設(shè)計(jì)線程池是Java并發(fā)里的重點(diǎn)工程題。ThreadPoolExecutor有七個(gè)參數(shù)核心線程數(shù)、最大線程數(shù)、空閑存活時(shí)間、存活時(shí)間單位、工作隊(duì)列、線程工廠、拒絕策略。筆試??嫉膮?shù)設(shè)計(jì)邏輯是這樣的核心線程數(shù)怎么定如果是CPU密集型通常設(shè)為CPU核心數(shù) 1如果是IO密集型設(shè)為CPU核心數(shù) * 2左右具體還涉及阻塞比例計(jì)算但筆試能答到這個(gè)層面已經(jīng)算優(yōu)秀。隊(duì)列怎么選LinkedBlockingQueue默認(rèn)無界任務(wù)堆積會(huì)撐爆內(nèi)存實(shí)際生產(chǎn)中建議用ArrayBlockingQueue有界隊(duì)列配合CallerRunsPolicy或自定義策略讓系統(tǒng)在過載時(shí)能反饋壓力。拒絕策略有四種AbortPolicy直接拋異常默認(rèn)、CallerRunsPolicy讓提交任務(wù)的線程自己執(zhí)行、DiscardPolicy靜默丟棄、DiscardOldestPolicy丟棄最舊任務(wù)。筆試如果問“哪種策略最安全”我會(huì)答CallerRunsPolicy因?yàn)樗粫?huì)丟任務(wù)同時(shí)通過讓調(diào)用線程執(zhí)行任務(wù)實(shí)現(xiàn)了天然限流這是面試官比較認(rèn)可的工程判斷。另外Executors工具類的幾個(gè)快捷方法存在隱患newFixedThreadPool和newSingleThreadExecutor用的無界隊(duì)列newCachedThreadPool的最大線程數(shù)是Integer.MAX_VALUE。阿里巴巴開發(fā)規(guī)范明確禁止使用這些快捷方法筆試?yán)锶绻麊枴盀槭裁床唤ㄗh用Executors創(chuàng)建線程池”這三點(diǎn)都要答出來。4. 手寫算法排序、邊界條件與復(fù)雜度分析4.1 快速排序的高頻考法與實(shí)現(xiàn)細(xì)節(jié)熱詞里同時(shí)出現(xiàn)了“快速排序java實(shí)現(xiàn)”和“冒泡排序java”說明排序算法在校招筆試?yán)锎_實(shí)?????焖倥判驇缀跏鞘謱懘a題的頭號(hào)選擇因?yàn)樗诠こ讨惺褂米顝V泛Arrays.sort()對(duì)基本類型用的就是雙軸快排??炫诺暮诵氖欠种芜x一個(gè)基準(zhǔn)值把數(shù)組分成左邊小于基準(zhǔn)、右邊大于基準(zhǔn)然后遞歸處理左右兩個(gè)子區(qū)間。關(guān)鍵點(diǎn)在partition這一步一個(gè)常見的實(shí)現(xiàn)是public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot arr[left]; int i left; int j right; while (i j) { while (i j arr[j] pivot) { j--; } while (i j arr[i] pivot) { i; } if (i j) { int tmp arr[i]; arr[i] arr[j]; arr[j] tmp; } } arr[left] arr[i]; arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }這段代碼有幾個(gè)容易寫錯(cuò)的地方。內(nèi)層兩個(gè)while必須加i j條件否則指針會(huì)越界arr[j] pivot和arr[i] pivot要帶等號(hào)否則遇到重復(fù)元素會(huì)死循環(huán)最后把基準(zhǔn)值放到i位置時(shí)要記得先把a(bǔ)rr[left]存下來否則基準(zhǔn)值被覆蓋。快排的平均復(fù)雜度是O(n log n)最壞是O(n2)。最壞情況發(fā)生在每次基準(zhǔn)值都是當(dāng)前區(qū)間的最小值或最大值比如數(shù)組已經(jīng)有序時(shí)。筆試如果問“如何避免最壞情況”標(biāo)準(zhǔn)答案有兩個(gè)一是隨機(jī)選取基準(zhǔn)二是取首、中、尾三個(gè)數(shù)的中位數(shù)作為基準(zhǔn)。這兩種方法都能把退化概率降到很低。4.2 冒泡排序與它的優(yōu)化形態(tài)冒泡排序本身不太會(huì)在筆試?yán)镒鳛槲ㄒ唤夥ǔ霈F(xiàn)但經(jīng)常作為復(fù)雜度分析或優(yōu)化題的載體。冒泡的基本思想是相鄰元素兩兩比較大的往后浮每一輪確定一個(gè)最大值的位置。public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) { break; } } }這個(gè)優(yōu)化點(diǎn)是如果某一輪沒有任何交換說明數(shù)組已經(jīng)有序直接跳出循環(huán)。最好情況下數(shù)組已有序時(shí)間復(fù)雜度降為O(n)。筆試也可能讓你比較冒泡和快排在“幾乎有序”的數(shù)組上的表現(xiàn)。注意雖然冒泡最好情況是O(n)但快排對(duì)已有序數(shù)組反而是最壞情況O(n2)這個(gè)對(duì)比是很好的加分回答。4.3 數(shù)組越界細(xì)節(jié)處的失分點(diǎn)熱詞里有“java中數(shù)組越界異常”這看著基礎(chǔ)但編寫碼題時(shí)因?yàn)檫吔缡д`導(dǎo)致的ArrayIndexOutOfBoundsException在筆試代碼里出現(xiàn)的頻率非常高。常見問題有幾類第一循環(huán)邊界出錯(cuò)。寫for (int i 0; i arr.length; i)多了一次訪問直接越界。寫二分查找時(shí)low high還是low high決定了區(qū)間是左閉右開還是左閉右閉。第二空數(shù)組和單元素?cái)?shù)組。很多算法題要求處理空數(shù)組時(shí)返回特定值但候選人默認(rèn)輸入至少有一個(gè)元素結(jié)果在真實(shí)測試用例上直接崩潰。寫代碼前先問自己數(shù)組為null怎么辦長度為0怎么辦長度為1時(shí)循環(huán)和遞歸能不能正確返回第三數(shù)組下標(biāo)計(jì)算溢出。經(jīng)典的二分查找寫法里mid (left right) / 2在left和right都很大時(shí)會(huì)溢出為負(fù)數(shù)應(yīng)該寫成left (right - left) / 2。這個(gè)坑看起來小但它曾經(jīng)在真實(shí)開源項(xiàng)目里引發(fā)過嚴(yán)重bug筆試出現(xiàn)時(shí)考察的就是你有沒有踩過這個(gè)坑。我建議筆試編程題統(tǒng)一采用“防御式寫法”開頭先判斷null和空數(shù)組循環(huán)條件統(tǒng)一用i length涉及下標(biāo)計(jì)算時(shí)優(yōu)先用減法替代加法。這些習(xí)慣看似笨拙但能幫你保住邊界用例的分?jǐn)?shù)。5. 框架與工程化從“會(huì)用”到“懂設(shè)計(jì)”5.1 Spring IoC與AOP的核心本質(zhì)校招候選人大多在學(xué)校做過Spring Boot項(xiàng)目但選擇題一旦問到底層原理很多人會(huì)露怯。Spring的兩個(gè)核心概念I(lǐng)oC控制反轉(zhuǎn)和AOP面向切面不是Spring發(fā)明的而是兩種設(shè)計(jì)思想Spring只是把它們的實(shí)現(xiàn)做到了極致。IoC的核心是對(duì)象之間的依賴關(guān)系不再由對(duì)象自己創(chuàng)建管理而是交給容器統(tǒng)一創(chuàng)建和注入。這樣做的好處是解耦類的創(chuàng)建和依賴裝配從代碼里挪到了配置中替換實(shí)現(xiàn)類不需要改動(dòng)業(yè)務(wù)代碼。筆試如果問“IoC和DI的區(qū)別”可以簡單回答IoC是一種設(shè)計(jì)原則DI依賴注入是它的實(shí)現(xiàn)方式之一。AOP用于處理跨越多個(gè)模塊的橫切關(guān)注點(diǎn)比如日志、事務(wù)、權(quán)限校驗(yàn)。它的底層依賴動(dòng)態(tài)代理如果目標(biāo)類實(shí)現(xiàn)了接口Spring使用JDK動(dòng)態(tài)代理如果沒有實(shí)現(xiàn)接口則使用CGLIB生成子類代理。這引出一個(gè)高頻題“Spring事務(wù)失效的場景有哪些”。常見答案包括方法不是public的、內(nèi)部方法自調(diào)用導(dǎo)致代理不生效、異常被catch掉沒有拋出、傳播行為設(shè)置錯(cuò)誤。每個(gè)問題背后都是對(duì)代理機(jī)制理解不透徹。Spring的Bean生命周期也是選擇題常客大體流程是實(shí)例化、屬性填充、各種Aware接口回調(diào)、BeanPostProcessor前置處理、初始化方法、BeanPostProcessor后置處理、使用、銷毀。理解這個(gè)順序很多“哪個(gè)方法先執(zhí)行”的題目都能答對(duì)。還有一個(gè)略偏但大廠愛問的Spring如何解決循環(huán)依賴。核心是三級(jí)緩存一級(jí)緩存存成品對(duì)象、二級(jí)緩存存半成品對(duì)象、三級(jí)緩存存對(duì)象工廠。兩個(gè)Bean互相依賴時(shí)A創(chuàng)建后先把半成品放入三級(jí)緩存B依賴A時(shí)從三級(jí)緩存拿到對(duì)象工廠生成早期引用B完成創(chuàng)建后A再完成屬性填充和初始化。理解了緩存的分級(jí)設(shè)計(jì)你就能回答“為什么用三級(jí)緩存兩級(jí)行不行”這類追問——兩級(jí)緩存可以提前暴露對(duì)象但無法處理AOP代理對(duì)象的生成時(shí)機(jī)問題。5.2 MySQL索引與事務(wù)隔離級(jí)別后端開發(fā)離不開數(shù)據(jù)庫MySQL是筆試最常見的考察對(duì)象。索引這塊B樹是絕對(duì)重點(diǎn)。為什么MySQL的InnoDB用B樹而不是B樹或紅黑樹因?yàn)锽樹只有葉子節(jié)點(diǎn)存數(shù)據(jù)內(nèi)部節(jié)點(diǎn)可以存放更多索引鍵樹更矮更寬磁盤IO次數(shù)更少同時(shí)葉子節(jié)點(diǎn)通過鏈表串聯(lián)范圍查詢非常高效。這個(gè)“為什么”比背出B樹的定義重要得多。最左前綴原則是索引題的高頻考點(diǎn)。聯(lián)合索引(a, b, c)能用到索引的查詢條件是a、a,b、a,b,c如果跳過了a直接查b或c索引就無法走。這背后的原因是B樹聯(lián)合索引的排序規(guī)則先按a排序a相同再按b排序跳過第一列后續(xù)列的排序關(guān)系無法支撐查詢。事務(wù)隔離級(jí)別那邊四種隔離級(jí)別里可重復(fù)讀是MySQL默認(rèn)的它能解決臟讀和不可重復(fù)讀但存在幻讀問題。InnoDB用MVCC多版本并發(fā)控制實(shí)現(xiàn)快照讀用next-key lock記錄鎖加間隙鎖解決部分幻讀。筆試常見的選擇題是判斷某個(gè)并發(fā)場景下會(huì)產(chǎn)生什么問題答題關(guān)鍵是先分清“當(dāng)前讀”和“快照讀”普通SELECT是快照讀SELECT ... FOR UPDATE、UPDATE、DELETE是當(dāng)前讀。5.3 開放設(shè)計(jì)題的答題框架試卷最后通常有一兩道綜合設(shè)計(jì)題比如“設(shè)計(jì)一個(gè)短鏈系統(tǒng)”或“設(shè)計(jì)一個(gè)秒殺接口”。這類題沒有標(biāo)準(zhǔn)答案但判卷人心里有清晰的評(píng)分線。我的建議是遵循一個(gè)固定框架來答先明確場景和核心指標(biāo)再拆解單機(jī)與分布式的不同方案最后指出可能的瓶頸和應(yīng)對(duì)手段。比如設(shè)計(jì)秒殺接口先定義幾個(gè)關(guān)鍵問題用戶量多大、庫存多少、必須保證哪些數(shù)據(jù)不能超賣。然后考慮接口層限流、MQ異步削峰、Redis預(yù)扣庫存、數(shù)據(jù)庫最終扣減。哪怕你的方案不完美只要每一步都說明了“為什么這樣做”就能拿到不錯(cuò)的分?jǐn)?shù)。這個(gè)框架不是八股文它是后端系統(tǒng)設(shè)計(jì)的通用思考方式任何系統(tǒng)都是先分析約束再做取舍。筆試設(shè)計(jì)題考察的是你遇到不確定性問題時(shí)的分析思路而不只是知識(shí)點(diǎn)的堆砌。6. 筆試現(xiàn)場常見問題與備考避坑實(shí)錄6.1 五個(gè)最容易翻車的現(xiàn)場細(xì)節(jié)我在網(wǎng)上看過不少考生對(duì)筆試的復(fù)盤結(jié)合熱詞里的幾個(gè)高頻問題整理一下現(xiàn)場最常踩的坑做成一張速查表。問題類型典型表現(xiàn)應(yīng)對(duì)建議環(huán)境問題本地編譯通過在線判題環(huán)境報(bào)錯(cuò)提前用OJ環(huán)境做一次模擬不要在IDE里寫完直接粘貼JDK版本差異“源發(fā)行版 17 需要目標(biāo)發(fā)行版 17”類報(bào)錯(cuò)確認(rèn)編譯級(jí)別與在線環(huán)境一致避免使用過新的語法特性控制臺(tái)亂碼中文輸出亂碼統(tǒng)一使用UTF-8編碼代碼文件不寫中文注釋時(shí)間分配失誤卡在一道算法題上導(dǎo)致后面的題沒時(shí)間做先通讀全卷先做有把握的題難題標(biāo)記后再回頭編譯器細(xì)節(jié)忘了import包、泛型寫錯(cuò)敲完代碼保留2分鐘做編譯自檢檢查import和語法環(huán)境變量配置和“vscode運(yùn)行java報(bào)錯(cuò)”也是熱詞里出現(xiàn)的高頻問題建議提前一天把本地的Java環(huán)境、編譯命令、常用庫的引入方式都驗(yàn)證一遍別讓環(huán)境問題影響了你的正常發(fā)揮。6.2 備考路線從八股到體系的三個(gè)階段很多準(zhǔn)備校招的人會(huì)陷入一個(gè)誤區(qū)刷了一堆“Java面試八股文”卻不知道自己到底掌握到什么程度。我的建議是把備考分成三階段。第一階段是知識(shí)掃描。把Java基礎(chǔ)、集合、并發(fā)、JVM、Spring、MySQL的知識(shí)點(diǎn)過一遍這個(gè)階段可以用資料和視頻目標(biāo)是建立知識(shí)地圖知道哪些模塊存在、彼此什么關(guān)系。第二階段是刻意練習(xí)。針對(duì)自己的薄弱項(xiàng)做題尤其是并發(fā)和算法題動(dòng)手寫代碼不能只看答案。第三階段是輸出復(fù)盤。嘗試不看任何資料把每個(gè)知識(shí)點(diǎn)講給自己聽或者寫成博客講不出來的地方就是還沒掌握的地方。這套流程我驗(yàn)證過很多次見效的關(guān)鍵不是投入時(shí)長而是“輸出”這個(gè)動(dòng)作。你背十遍HashMap原理不如親手寫一遍put的流程再畫一張它的底層結(jié)構(gòu)圖。6.3 關(guān)于Java學(xué)習(xí)路線的一點(diǎn)個(gè)人經(jīng)驗(yàn)熱詞里有不少“java學(xué)習(xí)路線”的搜索說明大家喜歡看看別人是怎么一步步走過來的。我自己的經(jīng)驗(yàn)是先搞定Java語法和面向?qū)ο笤偕钊爰虾蚃VM然后學(xué)并發(fā)和MySQL最后用Spring Boot做幾個(gè)能跑通的項(xiàng)目。這條路線不是唯一正確但勝在前后依賴清晰。注意不要在框架階段停留太久框架日新月異但底層的集合、并發(fā)、數(shù)據(jù)庫原理十年沒變過它們才是校招筆試真正的壓艙石。筆試這件事說到底是給自己做一次全面體檢。我見過很多候選人平時(shí)寫業(yè)務(wù)代碼很熟練但一遇到“為什么這樣設(shè)計(jì)”類型的題就卡住。原因很簡單日常開發(fā)里框架把大部分復(fù)雜度都封裝好了你不一定能接觸到它們背后的設(shè)計(jì)理由。而校招筆試恰恰是反過來的它不考你怎么用框架它考你能否理解框架背后的通用原理。坦白說一套筆試卷不可能測出一個(gè)人是否優(yōu)秀工程師的全部但它確實(shí)能測出知識(shí)體系的深度和廣度。把這份卷子里的每個(gè)考點(diǎn)都弄通之后你會(huì)發(fā)現(xiàn)不僅筆試更有把握日常開發(fā)里遇到奇怪問題時(shí)的排查思路也會(huì)清晰很多。這就是系統(tǒng)學(xué)習(xí)帶來的復(fù)利。如果你正在準(zhǔn)備校招希望這份拆解能幫你少走彎路。如果你已經(jīng)工作了不妨也拿這套題自查一下——很多知識(shí)點(diǎn)工作中用不到不代表不用掌握遇到線上詭異問題時(shí)你就知道它們有多重要了。