盤:基礎(chǔ)考點(diǎn)、算法設(shè)計(jì)與避坑指南)
2023年OPPO秋招后端崗筆試我踩過(guò)的坑和復(fù)盤思路又到一年秋招季后臺(tái)不少同學(xué)在問(wèn)OPPO的筆試難度和風(fēng)格。去年我完整參加了2023年OPPO秋招后端崗的筆試流程從收到筆試通知到做完最后一道題中間有幾個(gè)印象特別深的點(diǎn)也有不少事后復(fù)盤才想明白的細(xì)節(jié)。這篇不寫什么“通關(guān)秘籍”就老老實(shí)實(shí)把我自己遇到的題目類型、答題策略、以及踩過(guò)的坑整理出來(lái)給準(zhǔn)備沖后端崗的同學(xué)一個(gè)參考。先說(shuō)結(jié)論OPPO后端崗筆試的難度在主流大廠里算中上題型覆蓋很廣既有考察基礎(chǔ)功的選擇題也有需要完整思路的算法題和系統(tǒng)設(shè)計(jì)題。它不是單純刷題就能過(guò)的更看重你對(duì)后端知識(shí)體系的整體理解。適合正在準(zhǔn)備秋招的應(yīng)屆生、想跳槽后端崗的年輕工程師以及自學(xué)Java后端想檢驗(yàn)水平的朋友參考。1. 筆試整體流程與崗位匹配1.1 筆試基本信息與時(shí)間安排OPPO的秋招筆試一般在網(wǎng)申截止后一周內(nèi)分批發(fā)放我去年是投遞后第6天收到的筆試通知。整個(gè)筆試在??途W(wǎng)進(jìn)行時(shí)長(zhǎng)90分鐘題量大概在40道左右包含單選、多選、編程題和一道場(chǎng)景設(shè)計(jì)題。跟其他大廠不太一樣的是OPPO的筆試沒(méi)有單獨(dú)的客觀題部分和編程題部分拆分而是混在一起限時(shí)作答這意味著你需要自己控制節(jié)奏。時(shí)間分配上我復(fù)盤過(guò)最優(yōu)策略大概是選擇題部分控制在45分鐘內(nèi)編程題留30分鐘最后10分鐘處理系統(tǒng)設(shè)計(jì)題。這里有個(gè)很關(guān)鍵的認(rèn)知——系統(tǒng)設(shè)計(jì)題往往不是看你寫得多完整而是看你有沒(méi)有基本的工程思維但如果你前面選擇題浪費(fèi)太多時(shí)間后面設(shè)計(jì)題就只能交白卷非??上?。我當(dāng)時(shí)就是吃了這個(gè)虧前面幾道網(wǎng)絡(luò)和數(shù)據(jù)庫(kù)的多選題糾結(jié)太久導(dǎo)致最后一道設(shè)計(jì)題只寫了個(gè)大框架很多細(xì)節(jié)沒(méi)來(lái)得及展開估計(jì)扣了不少分。所以時(shí)間管理真的是筆試的第一道考題。1.2 崗位方向與考察重點(diǎn)后端崗在OPPO內(nèi)部其實(shí)分了好幾個(gè)方向有做互聯(lián)網(wǎng)服務(wù)的有做底層中間件的還有做設(shè)備端后臺(tái)的。不同方向的筆試題側(cè)重點(diǎn)會(huì)有差異但公共部分大致相同。我當(dāng)時(shí)投的是互聯(lián)網(wǎng)服務(wù)方向考察重點(diǎn)集中在Java基礎(chǔ)、并發(fā)編程、MySQL、Redis、消息隊(duì)列和分布式基礎(chǔ)這幾個(gè)模塊。還要提醒一點(diǎn)OPPO的筆試系統(tǒng)會(huì)記錄你的答題軌跡包括每道題的停留時(shí)間。雖然沒(méi)有實(shí)錘說(shuō)會(huì)分析這個(gè)但考慮到有些公司會(huì)看候選人是否在編程題上有異常操作建議還是老老實(shí)實(shí)做題不要想著切屏查資料。我認(rèn)識(shí)的一個(gè)同學(xué)就因?yàn)榍衅链螖?shù)太多被系統(tǒng)警告了雖然最后成績(jī)還行但流程上很被動(dòng)。2. 選擇題考點(diǎn)復(fù)盤基礎(chǔ)功底決定上限2.1 Java核心與并發(fā)編程選擇題里Java相關(guān)的占比最高大概有12道左右難度從簡(jiǎn)單到中等偏上都有。比較有代表性的幾類考點(diǎn)第一類是Java內(nèi)存模型和JMM可見性問(wèn)題。比如給你一段多線程代碼問(wèn)某個(gè)變量加了volatile之后哪些操作是原子的哪些不是。這種題表面上考volatile實(shí)際上考的是JMM的happens-before規(guī)則。我建議復(fù)習(xí)的時(shí)候一定要把JMM的八大操作和happens-before規(guī)則背熟特別是程序次序規(guī)則、管程鎖定規(guī)則、volatile變量規(guī)則這老三樣。第二類是線程池的參數(shù)組合。題目會(huì)給一個(gè)場(chǎng)景比如CPU密集型任務(wù)、IO密集型任務(wù)問(wèn)線程池核心線程數(shù)和最大線程數(shù)怎么設(shè)置。這里有個(gè)容易被忽略的點(diǎn)——任務(wù)隊(duì)列的選擇。SynchronousQueue適合非緩沖的提交模式LinkedBlockingQueue適合無(wú)界隊(duì)列模式ArrayBlockingQueue適合有界隊(duì)列。筆試往往不會(huì)直接告訴你用哪種隊(duì)列而是讓你根據(jù)場(chǎng)景推斷這就需要對(duì)線程池的整個(gè)工作流程很熟。第三類是synchronized和ReentrantLock的區(qū)別這個(gè)基本是必考的。重點(diǎn)關(guān)注可中斷性、公平鎖、超時(shí)獲取鎖、Condition等待隊(duì)列這些點(diǎn)。我印象很深的一道題是問(wèn)synchronized在JDK 6之后做了哪些優(yōu)化選項(xiàng)里有偏向鎖、輕量級(jí)鎖、自旋鎖、鎖消除、鎖粗化。這道題如果你只看過(guò)面試題總結(jié)沒(méi)看過(guò)源碼或者JVM的官方文檔很容易把鎖消除和鎖粗化搞混。2.2 網(wǎng)絡(luò)與操作系統(tǒng)基礎(chǔ)網(wǎng)絡(luò)部分的考點(diǎn)集中在TCP和HTTP。OPPO比較喜歡考TCP的擁塞控制流程特別是慢啟動(dòng)、擁塞避免、快重傳、快恢復(fù)這幾個(gè)狀態(tài)的轉(zhuǎn)換條件。有一道題我記得很清楚給了一個(gè)TCP連接傳輸過(guò)程中的擁塞窗口變化曲線要求判斷哪些階段對(duì)應(yīng)什么算法。這道題需要你對(duì)擁塞窗口的呈指數(shù)增長(zhǎng)和線性增長(zhǎng)階段有直觀理解不能只背概念。操作系統(tǒng)方面進(jìn)程線程的區(qū)別、死鎖產(chǎn)生的四個(gè)必要條件、虛擬內(nèi)存和頁(yè)面置換算法是高頻考點(diǎn)。有個(gè)比較偏的點(diǎn)是考了用戶態(tài)和內(nèi)核態(tài)的切換開銷問(wèn)哪些操作會(huì)導(dǎo)致狀態(tài)切換。系統(tǒng)調(diào)用、異常、外設(shè)中斷都會(huì)導(dǎo)致切換而普通的函數(shù)調(diào)用不會(huì)。這道題很多同學(xué)容易漏選異常和外設(shè)中斷因?yàn)槠匠?fù)習(xí)很少會(huì)專門記這個(gè)。HTTP部分考了HTTP/1.1和HTTP/2的區(qū)別包括多路復(fù)用、頭部壓縮、二進(jìn)制分幀這些特性。另外還考了GET和POST的本質(zhì)區(qū)別不是GET有長(zhǎng)度限制這種表面答案而是從RFC規(guī)范角度的語(yǔ)義差異。2.3 MySQL與Redis專項(xiàng)數(shù)據(jù)庫(kù)的題目占了大概8道是選擇題里的重頭戲。MySQL的考察集中在索引機(jī)制、事務(wù)隔離級(jí)別和鎖機(jī)制三塊。索引那部分考察點(diǎn)B樹為什么適合做索引、聚簇索引和非聚簇索引的區(qū)別、聯(lián)合索引的最左前綴原則、索引下推優(yōu)化。有一道題給了個(gè)SQL問(wèn)怎么建索引才能讓查詢效率最高選項(xiàng)里有單列索引、聯(lián)合索引、覆蓋索引的組合。這題的關(guān)鍵是先看WHERE子句的條件順序再看SELECT的字段是否能被覆蓋索引覆蓋兩個(gè)條件都滿足才是最優(yōu)解。事務(wù)隔離級(jí)別的題目是給幾個(gè)并發(fā)場(chǎng)景問(wèn)你分別會(huì)出現(xiàn)什么問(wèn)題。比如臟讀在哪種隔離級(jí)別下不會(huì)出現(xiàn)不可重復(fù)讀和幻讀的區(qū)別是什么。這里有個(gè)容易混淆的點(diǎn)——MySQL默認(rèn)的RR隔離級(jí)別下通過(guò)MVCC解決了快照讀的幻讀問(wèn)題但當(dāng)前讀還是會(huì)存在幻讀隱患。筆試如果問(wèn)到RR是否能完全避免幻讀一定要分快照讀和當(dāng)前讀兩種情況回答。Redis的考察集中在緩存策略和數(shù)據(jù)結(jié)構(gòu)。緩存穿透、緩存擊穿、緩存雪崩這三個(gè)概念是必考的題目會(huì)給你一個(gè)具體場(chǎng)景問(wèn)屬于哪種問(wèn)題以及對(duì)應(yīng)的解決方案。數(shù)據(jù)結(jié)構(gòu)方面比較喜歡考ZSet的底層實(shí)現(xiàn)是跳表哈希表以及跳表的插入和查找的時(shí)間復(fù)雜度。3. 編程題解析從暴力到最優(yōu)的演進(jìn)路徑3.1 第一道編程題數(shù)組類問(wèn)題編程題一共兩道第一道相對(duì)簡(jiǎn)單考的是數(shù)組相關(guān)的算法。我抽到的題目是給定一個(gè)未排序的整數(shù)數(shù)組找出其中沒(méi)有出現(xiàn)的最小的正整數(shù)。這題最直觀的解法是排序后遍歷時(shí)間復(fù)雜度O(nlogn)空間復(fù)雜度O(1)。但如果追求最優(yōu)解可以用原地哈希的思路把每個(gè)數(shù)放到它應(yīng)該在的位置上比如數(shù)字3應(yīng)該放在索引2的位置。遍歷一遍交換再遍歷一遍找缺失值時(shí)間復(fù)雜度O(n)空間復(fù)雜度O(1)。筆試時(shí)我只寫出了排序解法因?yàn)闀r(shí)間比較緊沒(méi)有往原地哈希的方向想。復(fù)盤的時(shí)候才發(fā)現(xiàn)這個(gè)解法其實(shí)很經(jīng)典在LeetCode上是原題41. 缺失的第一個(gè)正數(shù)如果提前刷過(guò)就不會(huì)丟這個(gè)分了。這里給個(gè)建議筆試前把LeetCode熱門100題中的數(shù)組類問(wèn)題都過(guò)一遍特別是原地哈希、雙指針、滑動(dòng)窗口這幾個(gè)套路。OPPO的編程題不太會(huì)出偏題怪題基本都是經(jīng)典題型的變種。3.2 第二道編程題動(dòng)態(tài)規(guī)劃與字符串第二道題明顯難度上了一個(gè)臺(tái)階考察的是字符串編輯距離問(wèn)題——給定兩個(gè)單詞word1和word2計(jì)算將word1轉(zhuǎn)換成word2所使用的最少操作數(shù)。這個(gè)考點(diǎn)很經(jīng)典在筆試中出現(xiàn)頻率很高。標(biāo)準(zhǔn)解法是二維DPdp[i][j]表示word1的前i個(gè)字符轉(zhuǎn)換成word2的前j個(gè)字符需要的最少操作數(shù)。狀態(tài)轉(zhuǎn)移方程分兩種情況如果word1[i-1]等于word2[j-1]那么dp[i][j] dp[i-1][j-1]如果不相等取插入、刪除、替換三種操作的最小值再加1。這題我沒(méi)寫出完整的多維代碼只寫了遞歸加備忘錄的版本。雖然邏輯上也對(duì)但筆試環(huán)境里沒(méi)有IDE提示遞歸的邊界條件寫起來(lái)容易出錯(cuò)浪費(fèi)了不少時(shí)間。復(fù)盤時(shí)我在本地用迭代方式重寫了一遍發(fā)現(xiàn)核心邏輯其實(shí)只有十幾行關(guān)鍵是要把二維數(shù)組的初始化做好特別是第一行和第一列的處理容易出錯(cuò)。3.3 編程題的答題策略與踩坑記錄編程題這塊有幾點(diǎn)教訓(xùn)想分享第一一定要先明確題目要求的輸入輸出格式。??途W(wǎng)的筆試和LeetCode不同需要自己處理輸入輸出。我第一道題就吃了這個(gè)虧題目要求處理多組輸入我默認(rèn)只處理了一組導(dǎo)致部分測(cè)試用例沒(méi)通過(guò)。第二時(shí)間分配上如果第二道題10分鐘內(nèi)沒(méi)有思路建議先寫一個(gè)暴力解保底能拿部分分?jǐn)?shù)。筆試通常有部分用例的分?jǐn)?shù)暴力解至少能過(guò)簡(jiǎn)單的測(cè)試用例總比空著強(qiáng)。第三注意溢出問(wèn)題。數(shù)組和字符串相關(guān)的題經(jīng)常涉及到大數(shù)相加或數(shù)值計(jì)算筆試環(huán)境是Java的話用long替代int能避免一些低級(jí)錯(cuò)誤。我當(dāng)時(shí)在編輯距離的狀態(tài)數(shù)組里就用了int如果字符串很長(zhǎng)dp數(shù)組的值可能超出int范圍這一點(diǎn)在反復(fù)確認(rèn)后果然踩到了。4. 數(shù)據(jù)庫(kù)與系統(tǒng)設(shè)計(jì)題工程思維的試金石4.1 數(shù)據(jù)庫(kù)場(chǎng)景題索引設(shè)計(jì)與SQL優(yōu)化最后一道大題是場(chǎng)景題給了一個(gè)實(shí)際業(yè)務(wù)背景一個(gè)電商系統(tǒng)的訂單表數(shù)據(jù)量在千萬(wàn)級(jí)別包含訂單ID、用戶ID、訂單狀態(tài)、創(chuàng)建時(shí)間、支付時(shí)間、訂單金額等字段。然后給了一批常見的查詢場(chǎng)景讓你設(shè)計(jì)合理的索引方案。這類題目的核心考察點(diǎn)有兩塊一是對(duì)聯(lián)合索引和覆蓋索引的理解二是對(duì)索引失效場(chǎng)景的敏感度。我當(dāng)時(shí)的方案是對(duì)用戶ID和創(chuàng)建時(shí)間建聯(lián)合索引因?yàn)樽畛R姷牟樵兪遣槟硞€(gè)用戶的訂單列表并按照時(shí)間排序?qū)τ唵螤顟B(tài)和支付時(shí)間建聯(lián)合索引因?yàn)檫\(yùn)營(yíng)側(cè)經(jīng)常需要統(tǒng)計(jì)某段時(shí)間內(nèi)某個(gè)狀態(tài)的訂單數(shù)量。復(fù)盤時(shí)想到的幾個(gè)加分點(diǎn)可以在索引設(shè)計(jì)里考慮降序索引比如創(chuàng)建時(shí)間按DESC建索引避免文件排序?qū)τ谟唵谓痤~這種范圍查詢要注意索引的選擇性如果某個(gè)狀態(tài)的值分布非常不均勻單獨(dú)建索引可能反而不如全表掃描。這些細(xì)節(jié)如果能在筆試中寫出來(lái)會(huì)很加分。4.2 分布式場(chǎng)景題緩存與一致性除了數(shù)據(jù)庫(kù)設(shè)計(jì)還有一道分布式的場(chǎng)景題背景是用戶積分系統(tǒng)要求支持高并發(fā)讀寫并保證一定程度的最終一致性。這題問(wèn)的是緩存策略選擇、緩存與數(shù)據(jù)庫(kù)的一致性方案、以及積分扣減的冪等性設(shè)計(jì)。這里我踩了一個(gè)坑——只考慮了本地緩存但實(shí)際上在分布式場(chǎng)景下本地緩存的失效通知是個(gè)大問(wèn)題。標(biāo)準(zhǔn)答案應(yīng)該先明確應(yīng)用場(chǎng)景再給出多級(jí)緩存方案本地緩存做一級(jí)緩存Redis做二級(jí)緩存數(shù)據(jù)庫(kù)做最終存儲(chǔ)。數(shù)據(jù)更新時(shí)采用Cache Aside Pattern先更新數(shù)據(jù)庫(kù)再刪除緩存。積分扣減的冪等性設(shè)計(jì)也是核心考點(diǎn)。我當(dāng)時(shí)只想到用分布式鎖但缺少了唯一ID的冪等校驗(yàn)這在面試評(píng)審中是很大的扣分點(diǎn)。后來(lái)復(fù)盤時(shí)梳理了更完整的方案通過(guò)請(qǐng)求唯一ID做冪等表先查冪等表再執(zhí)行業(yè)務(wù)邏輯最后更新冪等表三步驟放在同一個(gè)事務(wù)里。這個(gè)思路在多個(gè)場(chǎng)景中都經(jīng)常使用值得反復(fù)練習(xí)。4.3 設(shè)計(jì)題的時(shí)間分配與答題框架之前提到我在這道設(shè)計(jì)題上時(shí)間不夠這里分享一個(gè)我自己總結(jié)的答題框架方便在有限時(shí)間內(nèi)快速組織答案第一步明確業(yè)務(wù)場(chǎng)景的數(shù)據(jù)量、并發(fā)量讀寫比例、延遲要求這是一個(gè)優(yōu)秀設(shè)計(jì)的前提第二步畫核心架構(gòu)圖標(biāo)注哪些是存儲(chǔ)層、緩存層、業(yè)務(wù)層不需要畫得很細(xì)但層次要清晰第三步針對(duì)核心問(wèn)題逐點(diǎn)說(shuō)明比如緩存策略、一致性方案、冪等處理、分庫(kù)分表策略第四步點(diǎn)出可能的瓶頸和優(yōu)化方向比如熱key問(wèn)題、大key問(wèn)題、慢查詢治理顯示你的思考深度這套框架前兩步控制在5分鐘第三步10分鐘第四步5分鐘總共20分鐘基本夠用。如果再給我一次機(jī)會(huì)我就會(huì)按這個(gè)節(jié)奏來(lái)而不是在一開始就陷入細(xì)節(jié)。5. 筆試經(jīng)驗(yàn)總結(jié)與進(jìn)階建議5.1 一份好用的復(fù)習(xí)清單按照2023年OPPO秋招的筆試考察范圍結(jié)合熱詞里的高頻內(nèi)容我整理了一份自測(cè)清單每個(gè)知識(shí)點(diǎn)都能不打磕絆地講清楚再上考場(chǎng)Java基礎(chǔ)集合源碼特別是HashMap的put流程和擴(kuò)容機(jī)制、JVM內(nèi)存模型與GC算法、反射與代理、泛型擦除并發(fā)編程synchronized與ReentrantLock的底層實(shí)現(xiàn)、volatile的內(nèi)存語(yǔ)義、線程池參數(shù)設(shè)計(jì)與飽和策略、CAS與ABA問(wèn)題、AQS原理Spring生態(tài)Spring Bean的生命周期、Spring Boot自動(dòng)配置原理、Spring事務(wù)傳播機(jī)制與失效場(chǎng)景MySQL索引數(shù)據(jù)結(jié)構(gòu)與優(yōu)化、事務(wù)隔離級(jí)別與MVCC、鎖機(jī)制記錄鎖、間隙鎖、臨鍵鎖、慢查詢排查Redis五種數(shù)據(jù)結(jié)構(gòu)的底層實(shí)現(xiàn)、持久化機(jī)制RDB與AOF、緩存穿透與雪崩的解決方案、分布式鎖的Redisson實(shí)現(xiàn)消息隊(duì)列Kafka的消息存儲(chǔ)機(jī)制、消費(fèi)者組與分區(qū)分配策略、如何保證消息不丟失不重復(fù)網(wǎng)絡(luò)基礎(chǔ)TCP三次握手四次揮手、TCP與UDP區(qū)別、HTTP與HTTPS、HTTP/2的新特性算法動(dòng)態(tài)規(guī)劃、回溯、貪心、圖的遍歷與最短路、字符串匹配、常見數(shù)據(jù)結(jié)構(gòu)的手寫實(shí)現(xiàn)以上這些基本上是后端崗位筆試的核心考點(diǎn)無(wú)論投哪家互聯(lián)網(wǎng)公司60%-70%的概率會(huì)遇到。5.2 系統(tǒng)設(shè)計(jì)題的日常訓(xùn)練方法系統(tǒng)設(shè)計(jì)題在筆試中占比不高但對(duì)后續(xù)面試很關(guān)鍵。平時(shí)可以利用零散時(shí)間做素材積累比如和朋友點(diǎn)外賣時(shí)想想外賣平臺(tái)的訂單架構(gòu)看短視頻時(shí)思考推薦系統(tǒng)的粗排和精排網(wǎng)上買東西時(shí)琢磨庫(kù)存扣減怎么保證不超賣。這種思考多了筆試遇到任何場(chǎng)景題都不會(huì)沒(méi)話說(shuō)。我個(gè)人的一個(gè)習(xí)慣是每周挑一個(gè)系統(tǒng)做文字版的架構(gòu)設(shè)計(jì)比如設(shè)計(jì)一個(gè)短鏈接系統(tǒng)、一個(gè)秒殺系統(tǒng)、一個(gè)實(shí)時(shí)彈幕系統(tǒng)寫完參考答案對(duì)比。堅(jiān)持幾個(gè)月后你會(huì)發(fā)現(xiàn)自己的系統(tǒng)設(shè)計(jì)能力有質(zhì)的飛躍。5.3 關(guān)于心態(tài)和臨場(chǎng)發(fā)揮筆試前一天最重要的是保證睡眠而不是拼命刷題。我在做OPPO筆試時(shí)因?yàn)榍耙惶彀疽顾㈩}導(dǎo)致第二天頭昏腦漲選擇題的正確率明顯不如模考時(shí)高。筆試開始后如果遇到卡殼的題超過(guò)兩分鐘沒(méi)有思路就先標(biāo)記跳過(guò)不要在一道題上耗太久。還有一個(gè)小細(xì)節(jié)??途W(wǎng)的筆試環(huán)境支持本地IDE調(diào)試但有些公司的筆試系統(tǒng)會(huì)禁用復(fù)制粘貼到IDE只能直接在線提交。OPPO當(dāng)時(shí)是可以使用本地IDE的不過(guò)提交代碼還是要粘貼回網(wǎng)頁(yè)里。建議提前在??途W(wǎng)上做一兩套模擬題熟悉整個(gè)操作流程避免因?yàn)椴皇煜きh(huán)境而手忙腳亂。從我自己和身邊人的經(jīng)驗(yàn)來(lái)看筆試過(guò)程是秋招中最容易遺憾的環(huán)節(jié)。技術(shù)能力再?gòu)?qiáng)如果因?yàn)闀r(shí)間分配、環(huán)境不熟或者心里緊張沒(méi)發(fā)揮好都很可惜。希望這篇復(fù)盤能讓你少踩幾個(gè)我踩過(guò)的坑。最后再說(shuō)一個(gè)很多經(jīng)驗(yàn)帖不會(huì)提的點(diǎn)筆試結(jié)束后不管自我感覺(jué)如何建議第一時(shí)間把題目里你猶豫過(guò)、不會(huì)的知識(shí)點(diǎn)記錄下來(lái)再順手搜一下最優(yōu)解。這個(gè)動(dòng)作雖然簡(jiǎn)單但對(duì)面試時(shí)候的深度追問(wèn)幫助非常大不少面試官會(huì)順著筆試題的考點(diǎn)繼續(xù)深挖。我就是靠著筆試后的復(fù)盤在下一輪面試中接住了關(guān)于Redis持久化和MySQL間隙鎖的連續(xù)追問(wèn)。祝大家都能拿到心儀的offer。