據(jù)開發(fā)筆試題B卷解析與備戰(zhàn)攻略)
又到秋招季總有學(xué)弟學(xué)妹跑來問我大數(shù)據(jù)開發(fā)方向的筆試題到底怎么準(zhǔn)備我的建議向來是同一個(gè)——先找一套完整的大廠真題從頭到尾認(rèn)真做一遍比漫無目的地刷幾個(gè)月短視頻里的“面試技巧”有用得多。如果你問哪套題最適合用來摸底我通常會(huì)推薦愛奇藝2019秋招大數(shù)據(jù)開發(fā)方向筆試題B。原因很簡單這套題覆蓋面夠廣Java基礎(chǔ)、并發(fā)、大數(shù)據(jù)組件、Linux命令、SQL、手寫代碼全都有難度梯度也算合理特別適合當(dāng)成一張“能力體檢表”來用。我當(dāng)年把這套題的回憶版翻來覆去做了三遍后來去其他公司面試時(shí)發(fā)現(xiàn)很多考點(diǎn)是相通的。今天就把我對這套題的分析、做題過程中查漏補(bǔ)缺的知識(shí)點(diǎn)以及從這套題里反推出來的秋招備戰(zhàn)思路一次性整理出來。不需要你基礎(chǔ)多好只要你有一定 Java 和 Hadoop 生態(tài)的使用經(jīng)驗(yàn)這篇文章就能幫你把零散的知識(shí)串成一條線。1. 這套筆試題為什么值得反復(fù)拆解1.1 從崗位JD反推考察重點(diǎn)想弄明白一套筆試題為什么這么出最直接的方法是看崗位 JD。大數(shù)據(jù)開發(fā)這個(gè)崗位在愛奇藝這類視頻平臺(tái)日常面對的絕不僅僅是“寫幾個(gè) MapReduce”那么簡單。視頻網(wǎng)站每天產(chǎn)生海量用戶行為日志點(diǎn)擊、播放、暫停、拖拽、搜索、評論、付費(fèi)這些日志經(jīng)過采集、清洗、ETL、數(shù)倉建模、指標(biāo)計(jì)算最終支撐推薦、廣告、會(huì)員運(yùn)營等業(yè)務(wù)方做決策。所以這個(gè)崗位要求的能力是復(fù)合的既要懂 Java 這種主力開發(fā)語言又要熟悉 Hadoop、Spark、Flink、Kafka 這套大數(shù)據(jù)生態(tài)組件還要能寫復(fù)雜的 SQL 和 Shell 腳本偶爾甚至要自己調(diào) JVM 參數(shù)、排查線上 OOM。愛奇藝這套筆試B的考點(diǎn)布局基本上就是按照這個(gè)能力模型來的。很多同學(xué)拿到試卷第一反應(yīng)是“怎么考這么多 Java 基礎(chǔ)”覺得大數(shù)據(jù)開發(fā)不應(yīng)該只考框架嗎這個(gè)認(rèn)知在面試?yán)锖艹蕴潯4髷?shù)據(jù)框架本身就跑在 JVM 上Hadoop、Spark、Flink、Kafka 的源碼全是 Java/Scala 寫的你如果連 HashMap 的結(jié)構(gòu)、并發(fā)編程的基本機(jī)制都說不清楚面試官很難相信你能讀懂框架源碼、能定位線上問題。所以 Java 基礎(chǔ)部分不是湊題數(shù)是篩人。1.2 題型分布與答題節(jié)奏從各個(gè)渠道流傳的回憶版來看B 卷大致分為四類單選題、多選題、簡答題、編程題。單選多選主要覆蓋 Java 基礎(chǔ)、并發(fā)、JVM、Linux 命令、SQL 語法、大數(shù)據(jù)組件原理簡答題一般會(huì)有一兩道讓你描述 MapReduce 流程、Spark 任務(wù)執(zhí)行流程或者數(shù)據(jù)傾斜解決方案的題編程題則是傳統(tǒng)的數(shù)據(jù)結(jié)構(gòu)與算法題偶爾會(huì)結(jié)合大數(shù)據(jù)場景。這里有一個(gè)很關(guān)鍵的答題節(jié)奏問題選擇題不能戀戰(zhàn)。每道選擇題的分值并不高但如果在兩道爭議題上死磕十分鐘后面編程題的時(shí)間就會(huì)被壓縮。我記得當(dāng)時(shí)給自己定的規(guī)則是單道選擇題最多兩分鐘拿不準(zhǔn)的先標(biāo)記等編程題寫完再回頭蒙。整套題里真正的拉分項(xiàng)永遠(yuǎn)是編程題和簡答題不要在基礎(chǔ)題上丟了西瓜撿芝麻。1.3 B 卷背后的“套卷機(jī)制”標(biāo)題里的B不是隨便標(biāo)的。大廠筆試通常會(huì)在同一時(shí)間安排多套試卷題目一樣但順序打亂或者抽選不同子題集目的就是防止前后排考生對答案。這也是為什么你在網(wǎng)上看到同樣的“2019秋招大數(shù)據(jù)開發(fā)筆試題”有人說是 A 卷、有人說是 B 卷題目卻大同小異。準(zhǔn)備的時(shí)候不用糾結(jié)自己考的是哪一套考點(diǎn)就那么多把核心知識(shí)點(diǎn)全部過一遍任何卷子都能應(yīng)付。2. Java與并發(fā)看起來是選擇題實(shí)則是淘汰主戰(zhàn)場2.1 為什么大數(shù)據(jù)開發(fā)要死磕 Java 基礎(chǔ)大數(shù)據(jù)開發(fā)的日常工作中Java 不是“偶爾用一下”的語言而是主力語言。你用 Spark 寫分析任務(wù)雖然可以用 Scala 或者 PySpark但閱讀源碼、調(diào)優(yōu)、排查問題最終都會(huì)落到 Java/JVM 層面。愛奇藝這套筆試題里Java 相關(guān)題目占比相當(dāng)高而且考察得非常細(xì)。這不是為難人而是想篩掉那些只背框架 API、不懂底層原理的簡歷選手。我之前幫部門做過校招面試發(fā)現(xiàn)一個(gè)明顯的規(guī)律Java 基礎(chǔ)扎實(shí)的人大數(shù)據(jù)框架學(xué)得也快因?yàn)?Spark 的 RDD、DataFrame 也好Flink 的 Watermark、Checkpoint 也罷本質(zhì)上都是 Java 并發(fā)、集合、網(wǎng)絡(luò)編程的封裝。你明白 HashMap 的擴(kuò)容原理就更容易理解 Spark 的 shuffle 為什么會(huì)產(chǎn)生大量小文件你明白 JVM 內(nèi)存模型就更容易理解為什么大數(shù)據(jù)任務(wù)要設(shè)置 executor memory 參數(shù)。2.2 HashMap、ConcurrentHashMap 與并發(fā)安全的底層邏輯集合框架是 Java 基礎(chǔ)題里的“必考點(diǎn)”愛奇藝這套題也不例外。比較有代表性的考察方式是這樣的問題JDK 7 和 JDK 8 中 HashMap 的實(shí)現(xiàn)有哪些差異為什么 HashMap 在并發(fā)場景下不安全這個(gè)題幾乎每年都出現(xiàn)但能答全的人不多。正確的答題框架應(yīng)該是JDK 7 的 HashMap 底層是數(shù)組加鏈表插入元素時(shí)采用頭插法擴(kuò)容時(shí)多線程并發(fā) put 可能形成環(huán)形鏈表一旦 get 的時(shí)候發(fā)生死循環(huán)CPU 直接飆滿。JDK 8 改成了數(shù)組加鏈表加紅黑樹鏈表長度超過 8 且數(shù)組長度超過 64 時(shí)樹化插入改用尾插法從機(jī)制上避免了死循環(huán)問題但并發(fā)場景下仍然可能出現(xiàn)數(shù)據(jù)覆蓋、size 計(jì)數(shù)不準(zhǔn)確等問題。所以并發(fā)場景要用 ConcurrentHashMap。如果再追問 ConcurrentHashMap 的原理JDK 7 是分段鎖把整個(gè) Map 分成 16 個(gè) Segment每個(gè) Segment 是一把鎖JDK 8 放棄分段鎖改用 CAS 加 synchronized 鎖住桶的首節(jié)點(diǎn)鎖粒度更細(xì)并發(fā)度更高。這種層層遞進(jìn)的答法比背幾句“線程安全基于 CAS”的面試話術(shù)要顯得有底氣得多。2.3 JVM 與 GC 的考察方式JVM 相關(guān)題目在 B 卷里也占據(jù)一席之地常見考法有內(nèi)存區(qū)域劃分、GC 算法、OOM 場景分析。這類題看起來很“后端”但大數(shù)據(jù)開發(fā)同樣躲不開因?yàn)榕?Spark/Flink 任務(wù)時(shí)OOM 是出現(xiàn)頻率最高的線上事故之一。我推薦用“畫圖加舉例”的方式復(fù)習(xí)把堆內(nèi)存、虛擬機(jī)棧、本地方法棧、方法區(qū)、程序計(jì)數(shù)器的職責(zé)先講清楚然后結(jié)合一個(gè)具體場景——比如一個(gè) Spark Executor 頻繁 Full GC你會(huì)怎么排查一般思路是先用jstat看 GC 頻率再用jmap導(dǎo)出堆內(nèi)存快照用 MAT 分析哪個(gè)對象占內(nèi)存最大。如果你能在筆試簡答題里寫出這個(gè)排查鏈路面試官對你的印象會(huì)明顯不一樣。GC 算法方面CMS 和 G1 的區(qū)別是高頻考點(diǎn)。CMS 基于標(biāo)記清除并發(fā)收集但會(huì)產(chǎn)生內(nèi)存碎片G1 把堆劃分成 Region基于 Region 做局部回收可以預(yù)測停頓時(shí)間。JDK 9 之后 G1 成為默認(rèn)垃圾回收器JDK 17 以后 ZGC 開始普及這些演進(jìn)脈絡(luò)也可以順帶了解。2.4 數(shù)組與指針從 Java 數(shù)組到 C 指針的延伸搜這套題的同學(xué)經(jīng)常會(huì)連著搜“數(shù)組和指針筆試題”說明這類題在筆試?yán)锍霈F(xiàn)頻率很高。Java 里沒有指針的概念但數(shù)組本身就是一種引用類型所以考題會(huì)圍繞數(shù)組的拷貝、引用傳遞、內(nèi)存分配展開。比如問題int[] a {1,2,3}; int[] b a; b[0] 100;此時(shí)a[0]是多少答案是 100。因?yàn)閎 a賦值的是引用兩個(gè)變量指向同一塊堆內(nèi)存修改 b 的元素等于修改 a 的元素。這是 Java 基礎(chǔ)題里最經(jīng)典的坑也是很多非科班同學(xué)容易忽略的點(diǎn)。C 語言的指針與數(shù)組又是另一套完全不同的玩法數(shù)組名是常量指針a[i]本質(zhì)上是*(a i)的語法糖。雖然大數(shù)據(jù)開發(fā)崗位基本不會(huì)讓你寫 C但筆試偶爾會(huì)出這類題來考察計(jì)算機(jī)基礎(chǔ)是否扎實(shí)。應(yīng)對方式很簡單把指針加減、指針數(shù)組和數(shù)組指針、函數(shù)指針這幾個(gè)概念過一遍即可不需要深入。2.5 Java 基礎(chǔ)部分的備戰(zhàn)方式針對這套題折射出來的 Java 考點(diǎn)我的建議是不要只看面經(jīng)要自己動(dòng)手做實(shí)驗(yàn)。比如 HashMap 的樹化條件你就寫一段代碼往 HashMap 里插入哈希值相同的 key觀察鏈表什么時(shí)候變成紅黑樹再比如 volatile 的可見性你寫一個(gè)多線程程序驗(yàn)證一下不加 volatile 時(shí)死循環(huán)的現(xiàn)象。這些實(shí)驗(yàn)做完之后你會(huì)發(fā)現(xiàn)很多題不再需要“背答案”而是憑理解就能推導(dǎo)出來。3. 大數(shù)據(jù)組件原理MapReduce、Spark 與 Kafka 的考察重心3.1 視頻平臺(tái)的真實(shí)數(shù)據(jù)鏈路愛奇藝這類視頻平臺(tái)的數(shù)據(jù)鏈路很有代表性。以一次用戶播放行為為例客戶端上報(bào)播放日志到 KafkaFlink/Spark Streaming 消費(fèi) Kafka 數(shù)據(jù)進(jìn)行實(shí)時(shí)清洗與此同時(shí)離線任務(wù)把日志落盤到 HDFS通過 Hive/Spark SQL 做 ETL按天構(gòu)建數(shù)倉分層最終產(chǎn)出播放量、完播率、觀看時(shí)長等核心指標(biāo)。理解了這條鏈路你就明白為什么筆試題會(huì)同時(shí)考察 Kafka、Flink、Spark、Hive 這些組件。它們不是孤立的知識(shí)點(diǎn)而是同一套數(shù)據(jù)流轉(zhuǎn)過程中的不同環(huán)節(jié)??荚嚨臅r(shí)候別只顧著背每個(gè)組件單獨(dú)的特性能講清楚一條數(shù)據(jù)從產(chǎn)生到最終形成報(bào)表的完整流轉(zhuǎn)過程才是面試官真正想看到的。3.2 MapReduce 與 Shuffle必考的流程題MapReduce 的 Shuffle 過程幾乎是大數(shù)據(jù)筆試的“釘子戶”。愛奇藝這套題簡答題部分很可能會(huì)有這么一道描述一個(gè) MapReduce 任務(wù)從提交到完成的完整流程。答題不能只寫“Map 階段輸出中間結(jié)果Reduce 階段匯總”那樣太單薄。完整的回答應(yīng)該包含這幾個(gè)階段InputFormat 對輸入數(shù)據(jù)進(jìn)行切分生成 InputSplitRecordReader 將數(shù)據(jù)解析成 key-value 對Mapper 處理數(shù)據(jù)后map 輸出先寫入環(huán)形緩沖區(qū)默認(rèn)大小 100MB達(dá)到 80% 閾值時(shí)觸發(fā)溢寫溢寫前會(huì)做分區(qū)和排序默認(rèn)按 key 的哈希值分區(qū)溢寫會(huì)產(chǎn)生多個(gè)小文件之后執(zhí)行歸并排序合并成一個(gè)大文件同時(shí)做 Combiner 局部聚合Reduce 端通過 pull 方式拉取屬于自己的分區(qū)數(shù)據(jù)做一次完整的 shuffle然后再進(jìn)行分組排序最終調(diào)用 Reduce 函數(shù)。一個(gè)容易踩坑的細(xì)節(jié)是很多同學(xué)分不清“分區(qū)”和“分組”的區(qū)別。分區(qū)是決定某個(gè) key 進(jìn)入哪個(gè) Reduce 分區(qū)分組是決定哪些 key 值被認(rèn)為是同一個(gè) key從而調(diào)用一次 Reduce 方法。在 Hive 中g(shù)roup by作用于分組而 MapReduce 的分區(qū)數(shù)決定 Reduce 個(gè)數(shù)兩者不是說一回事。3.3 Spark 核心RDD 依賴、Stage 劃分與數(shù)據(jù)傾斜Spark 的考察重點(diǎn)集中在 RDD 依賴關(guān)系、Stage 劃分、Spark SQL 和調(diào)優(yōu)方向。寬依賴和窄依賴的區(qū)別是必考題。窄依賴是指父 RDD 的每個(gè)分區(qū)最多被子 RDD 的一個(gè)分區(qū)使用典型操作有 map、filter、union寬依賴是指父 RDD 的每個(gè)分區(qū)可能被子 RDD 的多個(gè)分區(qū)使用典型操作有 groupByKey、reduceByKey、join。窄依賴的算子不需要 shuffle可以在同一個(gè) Stage 內(nèi)完成寬依賴需要 shuffle是 Stage 劃分的邊界。為什么這個(gè)知識(shí)點(diǎn)如此重要因?yàn)閿?shù)據(jù)傾斜通常就發(fā)生在寬依賴的算子上。比如用 groupByKey 做 WordCount某個(gè)單詞出現(xiàn)次數(shù)特別多對應(yīng)的 Reduce 任務(wù)就會(huì)長時(shí)間運(yùn)行甚至 OOM。解決方案主要是加隨機(jī)前綴進(jìn)行二次聚合、兩階段聚合、調(diào)整并行度、開啟spark.sql.adaptive.enabled讓 Spark 自動(dòng)做傾斜 join 優(yōu)化。筆試題如果出“Spark 任務(wù)運(yùn)行緩慢你怎么排查”答題思路應(yīng)該是先看 Spark UI識(shí)別出哪個(gè) Stage 耗時(shí)最長再點(diǎn)進(jìn)去看是某個(gè) Task 卡住還是所有 Task 都慢如果是單個(gè) Task 慢基本可以斷定是數(shù)據(jù)傾斜再用sample算子抽樣檢查 key 分布最后根據(jù)傾斜情況選擇加隨機(jī)前綴或者調(diào)并行度。3.4 Flink 和 Kafka如果考到會(huì)這么出題雖然 2019 年的時(shí)候 Flink 還沒有如今這么普及但愛奇藝的實(shí)時(shí)計(jì)算場景早就存在了所以這套題里出現(xiàn) Flink 相關(guān)題目也不意外。Flink 常見的考點(diǎn)是事件時(shí)間與處理時(shí)間的區(qū)別、Watermark 的作用、狀態(tài)后端、Checkpoint 機(jī)制。Kafka 的考點(diǎn)相對更基礎(chǔ)分區(qū)與副本機(jī)制、ISR 與 ACK 參數(shù)acks0/1/all、消費(fèi)者組與分區(qū)分配策略、消息不丟失的保證。有一個(gè)很經(jīng)典的連環(huán)題“Kafka 如何保證消息不丟失”答題要從生產(chǎn)者、Broker、消費(fèi)者三個(gè)層面分別說明生產(chǎn)者設(shè)置acksall并開啟重試Broker 設(shè)置min.insync.replicas2消費(fèi)者處理完成之后再提交 offset。能分三層答基本能拿滿分。3.5 數(shù)倉分層與建模類簡答題大數(shù)據(jù)開發(fā)筆試?yán)镒畛R姷囊活惡喆痤}是談?wù)勀銈償?shù)倉是怎么分層的各層的作用是什么一般回答是 ODS、DWD、DWS、ADS 四層模型。ODS 層是源數(shù)據(jù)直接落地保持與業(yè)務(wù)庫一致不做任何加工DWD 層做清洗、規(guī)范化、維度退化統(tǒng)一命名規(guī)范DWS 層按主題匯總比如按用戶、商品、流量主題加工成寬表ADS 層是面向報(bào)表和應(yīng)用的數(shù)據(jù)直接對接業(yè)務(wù)方。這道題考察的不只是概念還有你對業(yè)務(wù)的理解。能結(jié)合視頻平臺(tái)舉出具體例子最好比如“播放記錄表屬于 ODS 層清洗后的播放明細(xì)表在 DWD 層按用戶維度匯總的觀看統(tǒng)計(jì)表在 DWS 層最終每日報(bào)表在 ADS 層”。筆試的時(shí)候如果能寫出這種層級加例子而不是只背四層定義是一個(gè)不小的加分項(xiàng)。4. Linux與SQL筆試中的“隱形大戶”4.1 Linux 命令題總是被低估很多同學(xué)備戰(zhàn)大數(shù)據(jù)筆試時(shí)會(huì)把精力放在算法題和框架原理上對 Linux 命令不屑一顧覺得“反正工作之后再學(xué)也不遲”。但大數(shù)據(jù)開發(fā)這個(gè)崗位日常工作的主戰(zhàn)場就是 Linux 服務(wù)器你不會(huì)用top、free、df、ps連排查問題都無從下手。所以筆試?yán)锍?Linux 題是非常合理的而且考察的點(diǎn)往往貼合實(shí)際場景。常見的出題方式不是問“l(fā)s命令的-l參數(shù)是什么意思”而是給你一個(gè)實(shí)戰(zhàn)場景服務(wù)器 CPU 飆升你怎么找出是哪個(gè)進(jìn)程干的標(biāo)準(zhǔn)答案是先用top查看進(jìn)程 CPU 占用率再用top -Hp [pid]查看具體線程配合jstack導(dǎo)出線程快照找到出問題的代碼位置。另一個(gè)高頻場景磁盤空間不足怎么快速找到大文件df -h看整體使用率du -sh *層層排查目錄再用find / -type f -size 1G直接列出大于 1GB 的文件。這些命令在大數(shù)據(jù)運(yùn)維里天天都要用筆試考它們就是考察日常積累。4.2 grep、awk、sed 三件套的實(shí)戰(zhàn)用法文本處理三件套是 Linux 命令題的重頭戲。比如日志文件access.log的每一行是“IP 地址 訪問時(shí)間 請求路徑 狀態(tài)碼”問你如何統(tǒng)計(jì)訪問次數(shù)最多的前 10 個(gè) IP。awk {print $1} access.log | sort | uniq -c | sort -rn | head -10這條命令的每一步都值得拆開講awk {print $1}是取第一列sort是排序讓相同的 IP 排在一起uniq -c統(tǒng)計(jì)去重后每項(xiàng)的出現(xiàn)次數(shù)sort -rn按數(shù)字逆序排序head -10取前十條。還有一類??碱}是 sed 的原地替換把文件里所有http替換成https并且修改原文件。sed -i s#http://#https://#g config.txt這里沒用常見的/作為分隔符而是用了#主要原因是 URL 里本身包含/直接用/做分隔符需要轉(zhuǎn)義寫成#可以少踩很多坑。這種細(xì)節(jié)就是筆試選擇題里的加分點(diǎn)。4.3 SQL 窗口函數(shù)分組 TopN 是最高頻考點(diǎn)大數(shù)據(jù)開發(fā)筆試的 SQL 題基本繞不開窗口函數(shù)。它的典型場景是求每個(gè)部門工資最高的員工、求每個(gè)用戶最近一筆訂單、求連續(xù)登錄 N 天的用戶。窗口函數(shù)的核心語法就一條row_number() over (partition by 分組字段 order by 排序字段 desc) as rk以“求每個(gè)用戶的最近 3 筆訂單”為例select user_id, order_id, order_time from ( select user_id, order_id, order_time, row_number() over(partition by user_id order by order_time desc) as rk from orders ) t where rk 3;這里特別需要提醒一個(gè)新手常犯的錯(cuò)誤子查詢里的rk字段不能在同一個(gè)查詢的 where 條件里直接引用比如where row_number() over(...) 3會(huì)直接報(bào)錯(cuò)因?yàn)榇翱诤瘮?shù)是最后執(zhí)行的。必須包一層子查詢在外面過濾。這個(gè)坑在筆試?yán)锍霈F(xiàn)頻率特別高因?yàn)樗疾斓氖菆?zhí)行順序的理解而不是語法背誦。4.4 連續(xù)登錄問題的兩種解法“求連續(xù)登錄 3 天以上的用戶”是另一道高頻 SQL 題它有很多變形比如連續(xù)簽到、連續(xù)購買。核心解法是用date_sub做日期差值。select user_id from ( select user_id, login_date, row_number() over(partition by user_id order by login_date) as rn from user_login group by user_id, login_date ) t group by user_id, date_sub(login_date, rn) having count(1) 3;思路是這樣的先把同一個(gè)用戶每天的連續(xù)登錄日期減去行號(hào)如果日期是連續(xù)的那么差值會(huì)保持不變?nèi)绻虚g斷了差值就會(huì)變。所以group by user_id, 差值之后count 大于等于 3 的組就是連續(xù)登錄至少 3 天的用戶。這里有個(gè)細(xì)節(jié)login_date可能同一天有多條記錄比如用戶一天登錄了兩次直接算行號(hào)會(huì)把同一天的重復(fù)記錄也算進(jìn)去導(dǎo)致誤判。所以要先group by user_id, login_date去重再做窗口計(jì)算。這個(gè)去重的步驟是很多參考答案里沒寫出來的但實(shí)際筆試時(shí)很容易中招。5. 編程題是拉分項(xiàng)從 TopN 到滑動(dòng)窗口的破題路徑5.1 編程題到底在考什么筆試的編程題不會(huì)讓你寫一個(gè)完整的 MapReduce也不會(huì)讓你手寫 Spark 算子。它的核心考察點(diǎn)還是數(shù)據(jù)結(jié)構(gòu)和算法只是偶爾會(huì)套一層“大數(shù)據(jù)場景”的外衣。愛奇藝這套題的編程部分基本集中在數(shù)組、字符串、鏈表、堆、滑動(dòng)窗口這些經(jīng)典題型上。刷題的時(shí)候不要盲目追求題量先把每一類題型的套路吃透。比如“連續(xù)子數(shù)組最大和”是動(dòng)態(tài)規(guī)劃基礎(chǔ)題“兩數(shù)之和”是哈希表的典型應(yīng)用“TopK”是堆的經(jīng)典場景“最長無重復(fù)子串”是滑動(dòng)窗口的標(biāo)準(zhǔn)模板。這四類題掌握之后筆試遇到新題至少不會(huì)完全懵。5.2 海量日志 TopK堆是最優(yōu)解結(jié)合大數(shù)據(jù)場景的編程題最常見的是“在一個(gè)很大的文件里找出出現(xiàn)次數(shù)最多的 TopK 個(gè)單詞”。純算法題版的問法是“求一個(gè)無序數(shù)組里的前 K 大元素”。public int[] topK(int[] nums, int k) { PriorityQueueInteger heap new PriorityQueue(k); for (int num : nums) { if (heap.size() k) { heap.offer(num); } else if (num heap.peek()) { heap.poll(); heap.offer(num); } } int[] res new int[k]; for (int i 0; i k; i) { res[i] heap.poll(); } return res; }這里用了一個(gè)大小固定為 K 的最小堆遍歷數(shù)組時(shí)只要當(dāng)前元素比堆頂大就替換堆頂這樣堆里始終維護(hù)著當(dāng)前最大的 K 個(gè)數(shù)時(shí)間復(fù)雜度 O(n log k)。面試如果追問“數(shù)據(jù)量特別大怎么辦”可以補(bǔ)充說明文件過大無法一次性加載到內(nèi)存時(shí)先做哈希分片把大文件拆成多個(gè)小文件分別統(tǒng)計(jì)每個(gè)小文件的 TopK最后再歸并。這道題還有一個(gè)高頻變種求第 K 大的元素。用快速選擇算法平均時(shí)間復(fù)雜度 O(n)代碼思路是在快排的 partition 基礎(chǔ)上只遞歸處理包含第 K 大的一側(cè)不做全量排序。如果筆試時(shí)間充裕寫快選比寫一堆更優(yōu)雅。5.3 連續(xù)子數(shù)組最大和動(dòng)態(tài)規(guī)劃入門模板“最大子數(shù)組和”是 LeetCode 第 53 題也是筆試編程題里出現(xiàn)頻率很高的一道因?yàn)樗绦【纺芸焖倏闯龊蜻x人的動(dòng)態(tài)規(guī)劃基本功。public int maxSubArray(int[] nums) { int cur nums[0]; int max nums[0]; for (int i 1; i nums.length; i) { cur Math.max(nums[i], cur nums[i]); max Math.max(max, cur); } return max; }核心邏輯就一行cur Math.max(nums[i], cur nums[i])。翻譯成人話就是“當(dāng)前最大子序列和”要么從當(dāng)前元素重新開始要么帶著前面的累加值繼續(xù)加取兩者較大值。cur維護(hù)的是以當(dāng)前元素結(jié)尾的子數(shù)組最大和max維護(hù)的是全局最大和。這道題寫出來很容易但想要在筆試?yán)锬脻M分還需要在注釋或者旁邊寫明“時(shí)間 O(n)空間 O(1)”這能體現(xiàn)你的算法復(fù)雜度意識(shí)。5.4 輸入輸出格式筆試翻車的高發(fā)區(qū)域代碼寫對了但是 0 分這類悲劇每年都在發(fā)生。原因絕大多數(shù)不是算法問題而是輸入輸出格式?jīng)]處理好。??途W(wǎng)這類平臺(tái)和 LeetCode 不同LeetCode 已經(jīng)幫你把函數(shù)簽名定義好了你只需要填函數(shù)體牛客網(wǎng)需要你寫完整的public class Main自己用Scanner讀取輸入再按指定格式輸出。很多同學(xué)平時(shí)只刷 LeetCode不熟悉這種“自己處理輸入輸出”的方式筆試時(shí)一緊張Scanner 的循環(huán)讀法都寫錯(cuò)了。我建議在秋招開始前去??途W(wǎng)上把近三年的真題模擬題都做幾道專門練習(xí)完整的代碼結(jié)構(gòu)。記住一個(gè)通用模板import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); int[] arr new int[n]; for (int i 0; i n; i) { arr[i] sc.nextInt(); } // 處理邏輯 System.out.println(result); } }還有一個(gè)容易被忽略的點(diǎn)有些題目要求輸出結(jié)果保留兩位小數(shù)比如System.out.printf(%.2f, result)有些要求多個(gè)結(jié)果之間用空格分隔最后一個(gè)后面不能有空格。這些細(xì)節(jié)在筆試環(huán)境里一旦出錯(cuò)會(huì)直接判定答案錯(cuò)誤比算法沒寫出來還可惜。6. 從這套筆試反推秋招備戰(zhàn)我的幾點(diǎn)實(shí)踐復(fù)盤6.1 原理與刷題的時(shí)間配比我見過太多人備戰(zhàn)大數(shù)據(jù)開發(fā)筆試要么只刷算法題要么只看框架面經(jīng)這倆都是極端。愛奇藝這套題給我的最大啟發(fā)是它同時(shí)考察原理深度和代碼熟練度兩邊的權(quán)重其實(shí)差不多。比較合理的安排是五五開一半時(shí)間用來深入理解 Hadoop、Spark、Kafka 的核心機(jī)制另一半時(shí)間用來刷算法題和 SQL 題。原理部分不要停留在“會(huì)用”層面要能用自己的話講清楚 MapReduce 的 Shuffle 過程、Spark 寬窄依賴、Kafka 的 ISR 機(jī)制。SQL 部分不要只看題解一定要親手在本地或者在線環(huán)境跑一遍。我自己復(fù)習(xí)時(shí)有一個(gè)笨但有效的方法把每個(gè)核心知識(shí)點(diǎn)抄在一張 A4 紙上只寫關(guān)鍵詞和流程箭頭不看資料對著這張紙口述講一遍。講不出來的地方就是知識(shí)盲區(qū)回頭再去看那塊的源碼或博客。這個(gè)方法堅(jiān)持兩周效果比反復(fù)讀書好得多。6.2 筆試現(xiàn)場的答題順序與時(shí)間切分真正坐在筆試考場里心態(tài)和平時(shí)刷題完全不一樣。我總結(jié)了一套當(dāng)時(shí)用著很順的答題順序先花一分鐘掃一遍全部題標(biāo)記出編程題的大致難度然后按順序做選擇題遇到卡殼的直接跳過選擇題做完后先做會(huì)寫的編程題再做簡答題最后回頭處理跳過的選擇題和自己不熟悉的編程題。時(shí)間切分上我一般會(huì)把整個(gè)筆試時(shí)間的 50% 留給編程題30% 留給選擇填空20% 留給簡答。因?yàn)榫幊填}是按通過用例給分的寫出來大部分用例可能就能拿到 60% 到 80% 的分?jǐn)?shù)這比在選擇題上糾結(jié)半天的性價(jià)比高得多。6.3 復(fù)盤比刷題更重要做完一套題對照答案估分只是第一步更重要的是把錯(cuò)題涉及的每一個(gè)知識(shí)點(diǎn)都深挖一遍。我會(huì)為每道錯(cuò)題建立一個(gè)類似“問題是什么、涉及的知識(shí)點(diǎn)、根本原因、同類題型的解題模板”的卡片然后每周集中回顧一次。比如我在做 HashMap 相關(guān)題時(shí)錯(cuò)過一次“JDK 7 頭插法和 JDK 8 尾插法的原因”這個(gè)點(diǎn)復(fù)盤的時(shí)候我就專門去看 JDK 8 的源碼把putVal方法整個(gè)讀了一遍又畫了擴(kuò)容前后鏈表結(jié)構(gòu)的示意圖。從那以后凡是遇到 HashMap 并發(fā)問題我基本不會(huì)再丟分。這種“以題帶點(diǎn)、以點(diǎn)帶面”的復(fù)盤方式比再刷十套新題更有效果。6.4 最后分享一點(diǎn)心態(tài)上的體會(huì)準(zhǔn)備秋招筆試的過程確實(shí)枯燥尤其是當(dāng)你發(fā)現(xiàn)同一道題做了三遍還是會(huì)錯(cuò)的時(shí)候很容易自我懷疑。但大數(shù)據(jù)開發(fā)這個(gè)方向本來考查的就是知識(shí)廣度和深度并重一時(shí)半會(huì)兒記不全太正常了。我自己的體會(huì)是把每一次筆試都當(dāng)成一次免費(fèi)的學(xué)習(xí)機(jī)會(huì)題沒做完不要緊關(guān)鍵是從中提煉出哪些知識(shí)點(diǎn)還沒掌握、哪些代碼模板還不夠熟練下一次進(jìn)場多拿幾分就足夠了。