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