網(wǎng)軟件工程師筆試核心考點全解析:C語言/Linux/網(wǎng)絡與算法實戰(zhàn))
2019年那會兒新能源造車新勢力的春招熱度正高小鵬汽車的互聯(lián)網(wǎng)中心放出了車聯(lián)網(wǎng)軟件工程師的崗位。我當時投了簡歷筆試那一關給我的印象特別深——題目范圍很寬從C語言指針到Linux多線程再到網(wǎng)絡協(xié)議和Java基礎幾乎把車聯(lián)網(wǎng)軟件工程師日常要碰的技術棧篩了一遍。這篇就結合我自己的答題經(jīng)歷和后來帶人的經(jīng)驗把這份筆試題背后的考察邏輯、核心考點和完整解題思路拆開講。無論你現(xiàn)在準備投車聯(lián)網(wǎng)崗、嵌入式方向還是想看看造車新勢力的技術筆試到底考什么這篇都有參考價值。1. 崗位與筆試全景拆解車聯(lián)網(wǎng)軟件工程師這個職位在小鵬互聯(lián)網(wǎng)中心里的定位其實是比較跨界的一塊。它既要懂車端嵌入式設備如T-Box、車機、網(wǎng)關控制器的運行邏輯又要熟悉網(wǎng)絡通信和云端平臺之間的數(shù)據(jù)交互。所以筆試不會只考某一類語言而是把C/C、Linux、計算機網(wǎng)絡、Java、數(shù)據(jù)結構與算法全糅在一張卷子里考察的是綜合基礎能力。1.1 車聯(lián)網(wǎng)軟件工程師到底做什么先說清楚崗位本身。車聯(lián)網(wǎng)軟件工程師核心工作對象是車端與云端、車端與車端、車端與手機端之間的通信和數(shù)據(jù)處理。常見落地點包括T-Box車載遠程通信終端上的通信程序開發(fā)負責車輛狀態(tài)上報、遠程控制指令接收。車機端的網(wǎng)絡模塊、診斷模塊UDS診斷開發(fā)和調試。OTA遠程升級系統(tǒng)中的下載、校驗、斷點續(xù)傳邏輯開發(fā)。車端與云端消息通道的維護比如基于MQTT或HTTP的報文交互。定位、Wi-Fi、藍牙、4G/5G等通信模塊的適配。這意味著車聯(lián)網(wǎng)軟件工程師日常要面對的核心技術棧是嵌入式Linux環(huán)境下的C/C開發(fā)加上網(wǎng)絡編程、多線程并發(fā)處理還要能寫一些支撐工具或平臺聯(lián)調的腳本、Java服務等。所以筆試考C、Linux、網(wǎng)絡、Java、數(shù)據(jù)結構完全是對著崗位的真實工作內(nèi)容設計的。1.2 2019春招筆試題型結構與側重點我拿到的這套題整體時間是120分鐘分三大部分單選題和填空題大概20道覆蓋面很廣。重點集中在C語言基礎指針、數(shù)組、內(nèi)存、Linux操作系統(tǒng)進程線程、內(nèi)存管理、常用命令和計算機網(wǎng)絡TCP/UDP、HTTP狀態(tài)碼、DNS等。編程題手寫代碼大概2到3道。涉及字符串處理、鏈表操作和數(shù)組算法。這一部分不允許用IDE純紙筆或者在線文本編輯框寫代碼所以對代碼規(guī)范性和邏輯完整性要求很高。簡答與場景設計題1道到2道車聯(lián)網(wǎng)方向特有的場景題比如“設計一個車輛狀態(tài)數(shù)據(jù)上報方案”或者“遠程控制指令的流程怎么設計”。從分值分布看C語言和Linux占了接近一半剩下的均勻分布在網(wǎng)絡、Java和算法題上。如果你只刷了LeetCode沒有系統(tǒng)準備嵌入式Linux和C語言細節(jié)這套卷子答起來會比較吃力。反過來如果只懂嵌入式而不會Java和網(wǎng)絡協(xié)議也會在后面的題目卡殼。1.3 為什么考察這些而非純Java或純嵌入式我當時也想過這個問題為什么一個互聯(lián)網(wǎng)中心的車聯(lián)網(wǎng)軟件工程師崗位筆試題要這么“雜”。后來入職接觸項目才明白車聯(lián)網(wǎng)是車端和互聯(lián)網(wǎng)的交叉點車端的T-Box或車機普遍是嵌入式Linux環(huán)境技術棧以C/C為主不可能只用Java。但車聯(lián)網(wǎng)又與云端平臺、手機App強關聯(lián)這些系統(tǒng)很多基于Java技術棧所以崗位需要具備閱讀和調試Java服務的能力。網(wǎng)絡協(xié)議是貫穿所有模塊的紐帶不懂TCP/UDP/MQTT車端和云端根本調不通。數(shù)據(jù)結構和算法是通用基本功任何軟件工程師都逃不掉。題目的“雜”本質是崗位要求本來就很雜。想進造車新勢力做車聯(lián)網(wǎng)就得接受這種“全棧偏嵌入式”的考察方式。2. 核心考點逐個拆解與拿分思路這份筆試題雖然年代稍早但考察的知識點都是車聯(lián)網(wǎng)軟件工程師面試題里的經(jīng)典基礎。換個角度看準備這套題就是準備整個行業(yè)的通用筆試。2.1 C語言與指針/數(shù)組車機端躲不開的基本功車端的C代碼量非常龐大指針和數(shù)組用不好寫出來的程序可能連編譯都過不了更別說穩(wěn)定運行。這套筆試題中指針和數(shù)組相關的題目大概有5到6道是拿分的重頭。2.1.1 數(shù)組名和指針的區(qū)別這是幾乎必考的題目。常見問法int arr[5] {1,2,3,4,5}; int *p arr;問sizeof(arr)和sizeof(p)分別是多少。答案是20和864位系統(tǒng)前者是數(shù)組總字節(jié)數(shù)后者是指針變量自身的大小。再深入一層arr1和arr1的區(qū)別也是一個高頻考察點arr1指向數(shù)組第二個元素相當于地址加4個字節(jié)。arr1指向整個數(shù)組后面相當于地址加20個字節(jié)。我答題時的思路是遇到數(shù)組和指針的區(qū)別先看操作對象是“整個數(shù)組”還是“數(shù)組首元素”再看指針的步長是基于什么類型計算的。數(shù)組名在表達式中可以隱式轉化為首元素指針但在sizeof和運算符下它代表的是整個數(shù)組。2.1.2 指針數(shù)組與數(shù)組指針這個區(qū)分是很多新手容易混淆的地方。筆試題大概率會寫一行聲明讓你判斷類型int *p1[5]; // 指針數(shù)組一個數(shù)組里面存了5個int*指針 int (*p2)[5]; // 數(shù)組指針一個指針指向含有5個int的數(shù)組考這道題的目的是看你讀代碼是否仔細。在車聯(lián)網(wǎng)開發(fā)中經(jīng)常要處理緩沖區(qū)、協(xié)議解析表指針數(shù)組用來存多個緩沖區(qū)的地址很常見數(shù)組指針則多用于二維數(shù)組的行遍歷。我當時總結了記憶方法先看優(yōu)先級[]的優(yōu)先級高于*所以int *p1[5]先結合[5]它是一個數(shù)組(*p2)先解除引用說明是一個指針。2.1.3 sizeof、strlen和內(nèi)存對齊字符串相關題目也高頻出現(xiàn)char str[] hello; char *p hello;sizeof(str) 6包含末尾的\0strlen(str) 5sizeof(p) 8指針大小strlen(p) 5這里有個容易踩的坑sizeof對指針和數(shù)組的結果完全不同。如果你聲明的是char *p hello再用sizeof(p)/sizeof(p[0])去算字符串長度結果永遠是8或4不是5。內(nèi)存對齊則是結構體題目的考點常見這樣出struct Node { char a; int b; char c; };問sizeof(struct Node)是多少。默認對齊規(guī)則下答案是12而不是6。因為int需要4字節(jié)對齊char a后面會填充3個字節(jié)b占4字節(jié)c占1字節(jié)后結構體整體還要對齊到4的倍數(shù)再填充3字節(jié)。實際開發(fā)中T-Box和車機之間通信經(jīng)常用結構體做報文解析內(nèi)存對齊導致的結構體大小意外我在工作中真的遇到過。2.1.4 const修飾指針的三種情況const int *p指針指向的值不可修改但指針本身可以改。int *const p指針本身不可修改但指向的值可以改。const int *const p兩者都不可修改。筆試里經(jīng)常用這種題考察基礎是否扎實。我的記憶訣竅是const修飾的是它右邊緊挨著的類型。const int *p里const修飾int所以指向的int值不能變int *const p里const修飾*p這個指針變量自身所以指針不能變。2.2 Linux與多線程嵌入式車機軟件的主戰(zhàn)場車機端的Linux系統(tǒng)和多線程編程幾乎是車聯(lián)網(wǎng)崗位面試筆試的必備內(nèi)容。小鵬這套題里Linux相關的題目占了不少尤其是進程線程、同步互斥、內(nèi)存管理這些。2.2.1 進程與線程的區(qū)別與聯(lián)系常見問題進程是資源分配的最小單位線程是CPU調度的最小單位。進程有獨立的地址空間線程共享進程的地址空間。進程間通信需要IPC機制管道、共享內(nèi)存、消息隊列、Socket線程間通信則可以直接讀寫共享變量但需要加鎖保護。進程切換開銷大線程切換開銷小。在車聯(lián)網(wǎng)場景里T-Box上的業(yè)務模塊通常會按功能拆成多個線程一個線程收網(wǎng)絡數(shù)據(jù)一個線程解析協(xié)議一個線程處理診斷邏輯一個線程定時上報車輛狀態(tài)。如果全用進程光是IPC開銷就夠喝一壺的。2.2.2 線程同步互斥鎖、讀寫鎖、條件變量筆試中會直接問“多線程并發(fā)訪問共享資源時如何保證線程安全”或者給一段代碼問你哪里有問題。核心要掌握mutex互斥鎖保證臨界區(qū)的獨占訪問但要注意死鎖問題。讀寫鎖允許多個讀者并發(fā)寫者獨占適合讀多寫少的場景。條件變量配合互斥鎖使用用于線程間的等待與通知。答題時要說清楚加鎖的粒度。如果鎖的范圍太大比如把整個業(yè)務邏輯全鎖住線程并發(fā)的優(yōu)勢就沒了如果鎖的范圍太小共享變量依然可能被并發(fā)修改。我當時的答法是用偽代碼展示pthread_mutex_lock(lock); // 臨界區(qū)更新車輛狀態(tài)結構體 state-speed speed; state-soc soc; pthread_mutex_unlock(lock);強調一點所有訪問共享變量的地方都要持同一把鎖而不是只在寫的時候加鎖、讀的時候不加否則會讀到中間狀態(tài)。2.2.3 死鎖產(chǎn)生的四個必要條件這也是容易被直接問到的互斥、持有并等待、不可剝奪、循環(huán)等待。答題時最好補充解決方案加鎖順序一致、避免嵌套鎖、使用超時機制、盡量使用trylock。這類題屬于概念題理解清楚就能拿分但真正在工程里排查死鎖往往要靠日志和gdb來抓現(xiàn)場。2.2.4 fork與僵尸進程這道題在嵌入式Linux方向出現(xiàn)頻率極高尤其是考察fork();之后父進程和子進程分別從哪里開始執(zhí)行、返回值是多少。子進程返回0父進程返回子進程PID。然后問如何避免僵尸進程用wait/waitpid收割子進程狀態(tài)或使用signal(SIGCHLD, SIG_IGN)忽略子進程退出信號。車聯(lián)網(wǎng)場景里比如OTA升級需要拉起一個子進程去下載固件如果父進程不及時回收子進程資源長時間運行后系統(tǒng)會積累大量僵尸進程最終拖垮整機。這個考點不是死記硬背而是工程上要面對的真實問題。2.3 計算機網(wǎng)絡與車聯(lián)網(wǎng)協(xié)議從TCP/IP到遠程控制網(wǎng)絡題在這套筆試中同樣占大頭。車聯(lián)網(wǎng)本身就是一個“把車連上網(wǎng)”的領域網(wǎng)絡協(xié)議理解不到位車端和云端、手機上的一堆功能都做不了。2.3.1 TCP三次握手和四次揮手問法通常很直接TCP建立連接為什么要三次握手斷開連接為什么需要四次揮手為什么TIME_WAIT狀態(tài)需要等待2MSL我的答題框架三次握手是為了防止失效的連接請求報文段突然又傳送到服務端導致資源浪費??蛻舳税l(fā)出SYN后服務端回復SYNACK客戶端再回復ACK雙方確認彼此的收發(fā)能力正常。四次揮手是因為TCP是全雙工通信每一方關閉自己的發(fā)送通道都需要單獨確認。客戶端發(fā)FIN表示“我發(fā)完數(shù)據(jù)了”服務端回復ACK表示“知道了”但服務端可能還有數(shù)據(jù)要發(fā)給客戶端所以不能同時關閉等服務端數(shù)據(jù)發(fā)完后再發(fā)FIN客戶端最后回復ACK連接才完全關閉。TIME_WAIT等待2MSL是為了保證最后一個ACK能夠到達服務端同時讓本連接產(chǎn)生的所有報文在網(wǎng)絡中消失避免干擾新連接。2.3.2 TCP與UDP的選擇車聯(lián)網(wǎng)里遠程控制指令比如遠程開空調、遠程鎖車對可靠性要求高一般用TCP或基于TCP的MQTT車輛位置、狀態(tài)數(shù)據(jù)這種允許偶發(fā)丟失但對實時性敏感的數(shù)據(jù)有些場景會用UDP。筆試里會給你場景問你用TCP還是UDP并說明理由。我總結的回答套路是先看數(shù)據(jù)是否允許丟失再看對延時的容忍度最后看是點對點還是多對多??刂祁?、文件傳輸類選TCP實時音視頻、高頻位置上報、廣播類可以選UDP。2.3.3 車聯(lián)網(wǎng)應用層協(xié)議MQTT、HTTP、CoAP這里會考察對車聯(lián)網(wǎng)消息協(xié)議的理解。T-Box上報車輛狀態(tài)到云端最常用的就是MQTT。它基于發(fā)布/訂閱模式消息通過Topic路由適合低帶寬、不穩(wěn)定網(wǎng)絡場景。答題時可以說清楚幾個要點MQTT支持三種QoS等級最多一次、至少一次、恰好一次。Broker代理服務器負責消息轉發(fā)車端作為客戶端發(fā)布消息云端服務訂閱消息。支持遺囑消息斷網(wǎng)時Broker能及時發(fā)現(xiàn)設備離線。HTTP則常見于一次性的請求響應場景比如手機App查詢車輛狀態(tài)、下發(fā)指令時App通過云平臺HTTP接口轉發(fā)。CoAP更輕量常用于資源受限設備但在車聯(lián)網(wǎng)里不如MQTT普及。2.3.4 經(jīng)典網(wǎng)絡問題HTTP狀態(tài)碼、DNS解析過程單選題容易出這類基礎題。比如200 OK表示請求成功301永久重定向302臨時重定向400請求錯誤401未認證403禁止訪問404資源不存在500服務器內(nèi)部錯誤502網(wǎng)關錯誤。DNS解析過程先查瀏覽器緩存再查本地hosts再查本地DNS服務器如果還沒有就逐級向上查詢根DNS、頂級域DNS、權威DNS。這些內(nèi)容看起來是“純八股”但實際聯(lián)調時看到WebServer返回的狀態(tài)碼如果不知道含義排查問題的效率會低很多。2.4 Java/JVM基礎互聯(lián)網(wǎng)中心的Java技術棧為什么互聯(lián)網(wǎng)中心的車聯(lián)網(wǎng)工程師也要考Java因為車聯(lián)網(wǎng)不是只寫車端程序還要和云平臺、運維工具、測試平臺打交道。這些平臺很多是Java寫的所以筆試會考察JVM、集合框架、并發(fā)編程基礎。2.4.1 JVM內(nèi)存區(qū)域與對象生命周期常見題目JVM運行時數(shù)據(jù)區(qū)分為哪些部分哪些線程共享哪些線程私有垃圾回收時如何判斷對象可回收我叫答法堆和方法區(qū)是線程共享的虛擬機棧、本地方法棧、程序計數(shù)器是線程私有的。對象主要分配在堆上通過可達性分析判斷是否存活GC Roots包括虛擬機棧中引用的對象、靜態(tài)變量、常量池引用等。車聯(lián)網(wǎng)場景中云端服務要處理大量車輛上報數(shù)據(jù)如果JVM參數(shù)不合理或者代碼里存在內(nèi)存泄漏服務跑幾天就OOM所有車都連不上這是很大的事故。2.4.2 HashMap底層原理Java題里HashMap幾乎必考底層是數(shù)組加鏈表紅黑樹默認容量16負載因子0.75。當鏈表長度超過8且數(shù)組長度大于64時鏈表轉為紅黑樹。put流程先計算key的hash定位到數(shù)組索引如果為空直接放入如果不為空遍歷鏈表key已存在則覆蓋不存在則尾插。擴容時重新計算hash并分配到新數(shù)組。答題時最好補充“為什么1.8要把頭插法改成尾插法”因為頭插法在并發(fā)擴容時可能形成環(huán)導致死循環(huán)尾插法能規(guī)避這個問題。雖然正常使用應該用ConcurrentHashMap但理解HashMap的線程不安全原因是面試官判斷你有沒有認真讀過源碼的分水嶺。2.4.3 線程池參數(shù)與執(zhí)行流程線程池核心參數(shù)核心線程數(shù)、最大線程數(shù)、空閑存活時間、工作隊列、拒絕策略。執(zhí)行流程是當提交任務時如果運行線程數(shù)小于核心線程數(shù)創(chuàng)建新線程否則任務進入隊列如果隊列滿了且運行線程數(shù)小于最大線程數(shù)創(chuàng)建新臨時線程如果超過最大線程數(shù)觸發(fā)拒絕策略。車聯(lián)網(wǎng)云端服務通常需要支撐大量車輛連接和指令下發(fā)線程池設計不好會出現(xiàn)線程爆炸、隊列積壓、請求超時。答這道題時可以順帶提一句拒絕策略的四種類型AbortPolicy丟棄并拋異常、CallerRunsPolicy調用者執(zhí)行、DiscardPolicy靜默丟棄、DiscardOldestPolicy丟棄最老任務。2.4.4 字符串常量池與String不可變性筆試題經(jīng)常問String s1 hello; String s2 new String(hello); s1 s2 的結果答案是false。s1指向字符串常量池中的對象s2指向堆中新建的對象雖然內(nèi)容相同但不是同一個引用。如果問s1.equals(s2)結果是true因為equals比較的是內(nèi)容。Java這部分的題并不難關鍵是拿捏住一個度你不需要像純Java崗那樣背到JVM調優(yōu)細節(jié)但JVM內(nèi)存模型、集合框架、并發(fā)基礎這三個板塊必須能答上來。3. 編程題實操復盤編程題是筆試里拉開差距的地方。車聯(lián)網(wǎng)軟件工程師筆試的編程題不像大廠算法崗那么“卷”不會出太偏的題但基礎題寫不寫得出來、寫得規(guī)不規(guī)范、邊界情況考慮得完不完整都很考驗功力。3.1 字符串類題目反轉、回文、最長子串字符串題是車聯(lián)網(wǎng)筆試題的??鸵驗檐嚩藚f(xié)議解析中字符串處理無處不在。典型題目有以下幾種3.1.1 字符串反轉題目給定一個字符串原地反轉。void reverseString(char *s, int len) { int left 0, right len - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right] tmp; left; right--; } }注意點必須是原地反轉不能額外開數(shù)組傳參時字符串長度是已知的如果是C風格字符串還要注意\0的位置。我當時寫的版本多了一個對len的判斷l(xiāng)en 1直接返回既能省事又避免越界訪問。3.1.2 判斷回文字符串題目判斷一個字符串是否是回文。bool isPalindrome(char *s, int len) { int left 0, right len - 1; while (left right) { if (s[left] ! s[right]) { return false; } left; right--; } return true; }這里面真正容易丟分的不是邏輯而是對邊界條件的處理。比如字符串長度為0或1時應該直接返回true如果題目要求忽略大小寫和空格還需要加過濾邏輯。筆試時如果題干沒有明確最好先問清楚或注釋說明假設條件。3.1.3 最長無重復子串這類題目稍微進階一點用滑動窗口解決int lengthOfLongestSubstring(char *s) { int hash[256] {0}; int left 0, right 0; int maxLen 0; int n strlen(s); while (right n) { hash[s[right]]; while (hash[s[right]] 1) { hash[s[left]]--; left; } if (right - left 1 maxLen) { maxLen right - left 1; } right; } return maxLen; }滑動窗口的思路右指針不斷向右擴展遇到重復字符時左指針向右收縮直到?jīng)]有重復字符。用哈希數(shù)組記錄窗口內(nèi)字符出現(xiàn)的次數(shù)。時間復雜度O(n)空間是O(1)因為字符集大小固定。3.2 鏈表類題目反轉、找環(huán)、合并有序鏈表鏈表題目在車聯(lián)網(wǎng)軟件工程師筆試里出現(xiàn)頻率也很高。車端設備內(nèi)存碎片多、動態(tài)分配頻繁鏈表是常用的數(shù)據(jù)結構。3.2.1 反轉鏈表題目反轉一個單鏈表。struct ListNode { int val; struct ListNode *next; }; struct ListNode* reverseList(struct ListNode *head) { struct ListNode *prev NULL; struct ListNode *curr head; while (curr ! NULL) { struct ListNode *next curr-next; curr-next prev; prev curr; curr next; } return prev; }核心思想是“三指針遍歷”用next暫存當前節(jié)點的下一個節(jié)點把curr-next指向前一個然后整體平移。這道題要特別注意反轉后返回的節(jié)點是原來的尾節(jié)點不是原來的頭節(jié)點。3.2.2 判斷鏈表是否有環(huán)經(jīng)典的快慢指針bool hasCycle(struct ListNode *head) { if (head NULL || head-next NULL) { return false; } struct ListNode *slow head; struct ListNode *fast head-next; while (fast ! NULL fast-next ! NULL) { if (slow fast) { return true; } slow slow-next; fast fast-next-next; } return false; }快指針每次走兩步慢指針每次走一步如果鏈表有環(huán)兩者必然相遇。注意快指針的判空條件要寫成fast ! NULL fast-next ! NULL防止空指針解引用。3.2.3 合并兩個有序鏈表遞歸和迭代兩種寫法都可以struct ListNode* mergeTwoLists(struct ListNode *l1, struct ListNode *l2) { if (l1 NULL) return l2; if (l2 NULL) return l1; if (l1-val l2-val) { l1-next mergeTwoLists(l1-next, l2); return l1; } else { l2-next mergeTwoLists(l1, l2-next); return l2; } }遞歸寫法代碼簡潔但要注意遞歸深度。如果鏈表很長可能棧溢出可以再準備一份迭代版本用虛擬頭節(jié)點dummy head簡化邊界處理。3.3 數(shù)組類題目二分查找、快排、滑動窗口數(shù)組題目是所有算法題的基礎筆試??伎嫉骄褪撬头诸}但如果寫不完整也容易丟分。3.3.1 二分查找題目在有序數(shù)組中查找目標值返回下標不存在返回-1。int binarySearch(int *nums, int n, int target) { int left 0, right n - 1; while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { return mid; } else if (nums[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }注意mid的計算要用left (right - left) / 2防止left right整形溢出。循環(huán)條件是left right還是left right取決于你定義的是閉區(qū)間還是左閉右開區(qū)間每道題可以固定用一種寫法避免臨場混亂。3.3.2 快速排序手寫快排也是高頻題void quickSort(int *nums, int left, int right) { if (left right) return; int i left, j right; int pivot nums[left]; while (i j) { while (i j nums[j] pivot) j--; if (i j) nums[i] nums[j]; while (i j nums[i] pivot) i; if (i j) nums[j--] nums[i]; } nums[i] pivot; quickSort(nums, left, i - 1); quickSort(nums, i 1, right); }寫快排時最需要小心的是內(nèi)層while的邊界條件i j的判斷不能丟同時比較時要用和否則遇到重復元素可能陷入死循環(huán)。我當時筆試時就在這個細節(jié)上多花了時間檢查。3.3.3 數(shù)組去重或移動零這類題目的思路是雙指針。移動零把數(shù)組中的0移動到末尾保持非0元素相對順序void moveZeroes(int *nums, int n) { int insertPos 0; for (int i 0; i n; i) { if (nums[i] ! 0) { nums[insertPos] nums[i]; } } while (insertPos n) { nums[insertPos] 0; } }這類題目不難關鍵是要展示出你考慮了原地操作、時間復雜度和空間復雜度。3.4 車聯(lián)網(wǎng)場景設計題車輛狀態(tài)定時上報這套筆試題里場景設計題讓我印象最深刻。它比普通算法題更貼近實際業(yè)務考察的是你把基礎技術應用到車聯(lián)網(wǎng)場景中的能力。題目大概是設計一個車輛狀態(tài)定時上報系統(tǒng)T-Box每隔一定時間如3秒將車輛位置、車速、電量、續(xù)航里程等狀態(tài)上報到云端要求描述整體架構、數(shù)據(jù)格式、上報策略和異常處理方法。我的答題思路分為四層架構層T-Box通過4G/5G模塊連接MQTT Broker云端的車聯(lián)網(wǎng)網(wǎng)關訂閱對應Topic數(shù)據(jù)經(jīng)過清洗后寫入數(shù)據(jù)庫供手機App查詢和運維平臺監(jiān)控。這里體現(xiàn)“車端-網(wǎng)絡-云端”的整體鏈條。數(shù)據(jù)格式層采用JSON或Protobuf定義字段包括車輛VIN碼、時間戳、上報序號、GPS坐標、車速、電量和續(xù)航里程。格式要向后兼容考慮加字段不破壞舊客戶端。上報策略層正常狀態(tài)3秒一次但要根據(jù)網(wǎng)絡狀況動態(tài)調整。弱網(wǎng)時降低上報頻率融合算法補傳。車輛熄火后進入休眠改為每10分鐘上報一次靜態(tài)狀態(tài)避免長時間喚醒T-Box導致蓄電池虧電。異常處理層消息中包含一個自增序號云端發(fā)現(xiàn)序號跳變說明有上報丟失可以觸發(fā)補傳機制。網(wǎng)絡斷開時車載端把狀態(tài)數(shù)據(jù)緩存在本地等網(wǎng)絡恢復后重傳。同時考慮流量成本緩存數(shù)據(jù)不宜過多可以使用壓縮算法減小包體。這道題的坑在于如果只寫一個“定時上報”的簡單方案得分不會高。面試官看得是你能不能考慮網(wǎng)絡抖動、車輛休眠、流量控制、消息時序、數(shù)據(jù)一致性這些實際問題。4. 車聯(lián)網(wǎng)工程場景題與系統(tǒng)設計場景設計題雖說不一定有標準答案但有一個隱藏的要求你設計的系統(tǒng)要能落地。車聯(lián)網(wǎng)環(huán)境下車輛可能在地下停車場、隧道、高速公路上網(wǎng)絡時好時壞設備算力又有限。如果方案里不考慮這些問題答得再漂亮也是空中樓閣。4.1 車輛狀態(tài)上報服務的消息格式與協(xié)議選型先說為什么選MQTT而不是直接走HTTP。HTTP是請求-響應模型車端要定時上報就得頻繁建立連接連接開銷大弱網(wǎng)下成功率低。MQTT是長連接加發(fā)布訂閱模式車端連上Broker后可持續(xù)發(fā)布消息服務器訂閱對應Topic即可連接建立一次后續(xù)復用省去了反復握手的開銷。消息體格式上我建議用JSON做演示但工程上更推薦Protobuf因為它體積小、序列化/反序列化性能高非常適合車載環(huán)境。筆試題如果沒規(guī)定用哪種你可以先給出JSON以便閱讀再補充“生產(chǎn)環(huán)境中可替換為Protobuf以降低流量”這樣顯得有實戰(zhàn)經(jīng)驗。字段設計時注意VIN碼標識車輛唯一身份相當于車的身份證。時間戳最好用Unix時間戳毫秒級避免時區(qū)問題。上報序號由車端自增用于檢測丟包和亂序。狀態(tài)字段要預留擴展位未來新增傳感器數(shù)據(jù)時不用改消息結構。增加協(xié)議版本號字段方便多版本共存。4.2 遠程控制指令的時序與安全遠程控制如遠程開啟空調、遠程解鎖、遠程鳴笛是車聯(lián)網(wǎng)的重要功能。設計題如果考察遠程控制關鍵在于時序設計和安全策略。完整時序用戶在手機App上點擊按鈕。App調云端指令接口云端鑒權確認用戶是車主且有權限。云端通過MQTT向指定車輛下發(fā)控制指令主題可設計為vehicle/control/{vin}。T-Box收到指令后校驗指令簽名再通過內(nèi)部CAN總線把控制請求轉發(fā)給車身的域控制器。域控制器執(zhí)行成功后把執(zhí)行結果返回給T-Box。T-Box上報執(zhí)行結果到云端云端推送通知給App。安全考量控制指令必須防重放攻擊消息里要帶時間戳和隨機數(shù)指令要簽名防止偽造下發(fā)云端和車端之間要使用TLS加密通信。這個問題如果只講到“發(fā)指令、收結果”不展開安全設計容易被判定為考慮不夠全面。4.3 OTA升級的可靠性和斷點續(xù)傳車聯(lián)網(wǎng)還有一個高頻場景設計題OTAOver-The-Air升級方案。難點在于固件包可能幾百MBLTE網(wǎng)絡不穩(wěn)定容易下載中斷。如果下載一半失敗了重頭再來成本太高。升級過程如果斷電或失敗車機可能變磚。答題重點放在斷點續(xù)傳和升級保護上。斷點續(xù)傳可以通過HTTP Range請求實現(xiàn)。車載終端先發(fā)送一個GET請求帶上Range: bytes1000-服務器從偏移1000字節(jié)開始繼續(xù)傳。也可以分段下載每段校驗CRC或MD5記錄已完成的分段索引下載完成后整體校驗收到的固件包再刷寫。刷寫階段要引入雙分區(qū)機制A/B分區(qū)系統(tǒng)先寫備分區(qū)全部寫完后校驗通過再切換啟動分區(qū)。這樣即使某次刷寫失敗系統(tǒng)還能從原分區(qū)正常啟動不會變磚。4.4 并發(fā)上報的帶寬和消息隊列問題如果題目再深一點會問“成千上萬輛車同時上報云端怎么扛得住”這個問題的核心是削峰填谷。車端上報數(shù)據(jù)不是均勻分布的早高峰、節(jié)假日出行時段上報量會突增。云端如果直接讓所有車輛都直連數(shù)據(jù)庫寫入數(shù)據(jù)庫很可能被打爆。常見方案是引入消息隊列Kafka/RocketMQ作為緩沖層。T-Box上報的數(shù)據(jù)先進入MQTT Broker再由云端網(wǎng)關程序消費并發(fā)送到消息隊列后端消費服務從消息隊列按吞吐量拉取數(shù)據(jù)批量寫入數(shù)據(jù)庫。消息隊列天然起到削峰的作用高峰期先積壓慢慢消費數(shù)據(jù)量過大時可以動態(tài)增加消費者實例提高消費速度。架構可以畫成T-Box - MQTT Broker - 數(shù)據(jù)接入服務 - Kafka - 流處理/批處理 - 數(shù)據(jù)庫/大數(shù)據(jù)平臺。畫圖不方便但在答題時把數(shù)據(jù)流向寫清楚考官能看出來你懂分布式系統(tǒng)的套路。5. 筆試避坑與備考建議筆試過了之后我復盤過整張卷子也跟同期進去的同事交流過。有一些分數(shù)是本來可以拿得更穩(wěn)的因為踩了不該踩的坑。這里整理出來算是給后來人提個醒。5.1 題型失分點題型高頻失分原因針對建議選擇題概念記混如指針數(shù)組和數(shù)組指針用符號優(yōu)先級分析不要裸背填空題sizeof和strlen混用養(yǎng)成先看類型的習慣編程題1字符串邊界條件不處理如空串寫完代碼后補充空、單元素、全重復用例編程題2鏈表快慢指針判空條件不全動手畫圖確認每一步都不會空指針編程題3數(shù)組二分查找邊界錯誤固定一套區(qū)間寫法反復練習場景設計題只寫“定時上報”未做異常分析按“正常流程異常流程降級策略”框架回答我在筆試中就吃了“sizeof和strlen”的虧導致選擇填空部分不如預期。這種東西不是不會而是考場上一緊張就容易想當然。平時練習時把易混點整理成手冊考前翻一遍效果比臨時刷題好得多。5.2 簡歷與筆試聯(lián)動準備筆試題的內(nèi)容往往和崗位描述高度相關。投遞車聯(lián)網(wǎng)軟件工程師前先把崗位JD里的關鍵詞全部列出來逐個準備。如果JD提到“熟悉嵌入式Linux環(huán)境下C/C開發(fā)”那C指針、Linux進程線程、內(nèi)存管理就是必考如果提到“熟悉網(wǎng)絡編程、TCP/IP協(xié)議”那TCP狀態(tài)、Socket編程、MQTT通信就是重點。我當時吃了個虧因為簡歷里寫的是“熟悉Java”差點把C的題目給輕視了。實際上簡歷里列舉的技能都會被筆試考官用來作為出題依據(jù)所以簡歷上寫了什么就要做好被問到對應知識點的準備。反過來筆試答題時也可以盡量把答案往自己擅長的方向引。比如編程題如果沒限定語言你可以選自己最熟悉的不必死磕C。我當時看到第一道編程題時題目要求用C/C實現(xiàn)但第二道場景題是純文字描述這就給了我發(fā)揮Java、網(wǎng)絡協(xié)議知識的機會。5.3 時間分配與心態(tài)120分鐘我的建議是選擇題和填空題控制在25到30分鐘不會的題先標記不戀戰(zhàn)。編程題每道控制在20分鐘以內(nèi)如果思路卡住了先寫能確定的部分至少拿過程分。場景設計題留出20到25分鐘這部分分值最高也最能拉開差距。最后用10分鐘檢查前面的標記題。車聯(lián)網(wǎng)崗位的筆試題整體難度不算天花板級別但覆蓋面廣它考的不是“你有沒有刷過原題”而是“你在實際寫車聯(lián)網(wǎng)業(yè)務代碼時會不會踩這些基礎坑”。我個人在實際操作中的體會是這套題最值錢的地方不是標準答案而是提醒你把基礎知識重新梳理成體系。后來我在T-Box上報模塊中遇到數(shù)據(jù)錯位問題排查時定位到結構體內(nèi)存對齊引發(fā)的大小不一致在OTA斷點續(xù)傳模塊里又是用HTTP Range的思路解決問題。筆試題里的那些考點最終都會在工作中重新遇見。所以準備筆試時不要只背答案盡量理解每個知識點在車聯(lián)網(wǎng)場景中的具體應用這比多刷十道題都有用。如果你現(xiàn)在正在準備車聯(lián)網(wǎng)軟件工程師的筆試我的建議是所有基礎題按“原理代碼工程場景”三個維度復習。原理保證你能應對選擇題代碼保證你能通過編程題工程場景保證你在設計題中有話說。三塊都覆蓋到這套筆試大概率能順利過關。