1. 程式人生 > >從“成都-go-戒炸雞”的面試題開始說起

從“成都-go-戒炸雞”的面試題開始說起

今天晚上“高效能伺服器開發”QQ群(群號:49114021,有興趣的讀者可以加一下)裡面一名叫“成都-go-戒炸雞”的群友提出了他最近面試的一些面試題,面試題內容個人覺得非常典型、也非常有代表性和針對性,故拿出來與大家分享一下,也感謝他的分享。成都-go-戒炸雞說:

“今天面試,我沒答出來的有redis持久化機制,redis銷燬方式機制,mq實現原理,c++虛擬函式,hash衝突的解決,memcached一致性雜湊,socket函式select的缺陷,epoll模型,同步互斥,非同步非阻塞,回撥的概念,innodb索引原理,單向圖最短路徑,動態規劃演算法。”

為了避免問題有歧義,面試題略有修改。

思路分析

從面試題的內容可以看出,這是一個後臺開發的職位。

除了關於 c++ 虛擬函式這個問題以外,其他的大多數問題都與哪種程式語言關係不大,大多數是原理性和基礎性的問題,少數是工作經驗問題,筆者試著給大家分析。

語言基礎

C++ 虛擬函式這是面試初、中級 C ++ 職位一個概率95%以上的面試題。一般有以下幾種問法:

  1. 在有繼承關係的父子類中,構建和析構一個子類物件時,父子建構函式和解構函式的執行順序分別是怎樣的?

  2. 在有繼承關係的類體系中,父類的建構函式和解構函式一定要申明為 virtual 嗎?如果不申明為 virtual 會怎樣?

  3. 什麼是 C++ 多型?C++ 多型的實現原理是什麼?

  4. 什麼是虛擬函式?虛擬函式的實現原理是什麼?

  5. 什麼是虛表?虛表的記憶體結構佈局如何?虛表的第一項(或第二項)是什麼?

  6. 菱形繼承(類D同時繼承B和C,B和C又繼承自A)體系下,虛表在各個類中的佈局如何?如果類B和類C同時有一個成員變了m,m如何在D物件的記憶體地址上分佈的?是否會相互覆蓋?

演算法與資料結構基礎

說到演算法和資料結構,對於社招人士和對於應屆生一般是不一樣的,對於大的網際網路公司和一般的小的企業也是不一樣的。下面根據我當面試官面試別人和找工作被別人面試經驗來談一談。

先說考察的內容,除了一些特殊的崗位,常見的演算法和資料結構面試問題有如下:

  1. 排序(常考的排序按頻率考排序為:快速排序 > 氣泡排序 > 歸併排序 > 桶排序)

一般對於對演算法基礎有要求的公司,如果你是應屆生或者工作經驗在一至三年內,以上演算法如果寫不出來,給面試官的影響會非常不好,甚至直接被 pass 掉。對於工作三年以上的社會人士,如果寫不出來,但是能分析出其演算法複雜度、最好和最壞的情況下的複雜度,說出演算法大致原理,在多數面試官面前也可以過的。注意,如果你是學生,寫不出來或者寫的不對,基本上面試過不了。

  1. 二分查詢

    二分查詢的演算法儘量要求寫出來。當然,大多數面試官並不會直接問你二分查詢,而是結合具體的場景,例如如何求一個數的平方根,這個時候你要能想到是二分查詢。我在2017年年底,面試agora時,面試官問了一個問題:如何從所有很多的ip地址中快速找個某個ip地址。

  2. 連結串列

    無論是應屆生還是工作年限不長的社會人士,璉表常見的操作一定要熟練寫出來,如連結串列的查詢、定位、反轉、連線等等。還有一些經典的問題也經常被問到,如兩個連結串列如何判斷有環(我在2017年面試餓了麼二面、上海黃金交易所一面被問過)。連結串列的問題一般不難,但是連結串列的問題存在非常多的“”,如很多人不注意邊界檢查、空連結串列、返回一個連結串列的函式應該返回連結串列的頭指標等等。

  3. 佇列與棧

    對於應屆生來說一般這一類問的比較少,但是對於社會人士尤其是中高階崗位開發,會結合相關的問題問的比較多,例如讓面試者利用佇列寫一個多執行緒下的生產者和消費者程式,全面考察的多執行緒的資源同步與競態問題(下文介紹多執行緒面試題時詳細地介紹)。

    棧一般對於基礎要求高的面試,會結合函式呼叫實現來問。即函式如何實現的,包括函式的呼叫的幾種常見呼叫方式、引數的入棧順序、記憶體棧在地址從高向低擴充套件、棧幀指標和棧頂指標的位置、函式內區域性變數在棧中的記憶體分佈、函式呼叫結束後,呼叫者和被呼叫者誰和如何清理棧等等。某年面試京東一基礎部門,面試官讓寫從0加到100這樣一個求和演算法,然後寫其彙編程式碼。

  4. 雜湊表

    雜湊表是考察最多的資料結構之一。常見的問題有雜湊衝突的檢測、讓面試者寫一個雜湊插入函式等等。基本上一場面試下來不考察紅黑樹基本上就會問雜湊表,而且問題可淺可深。我印象比較深刻的是,當年面試百度廣告推薦部門時,二面問的一些關於雜湊表的問題。當時面試官時先問的連結串列,接著問的雜湊衝突的解決方案,後來讓寫一個雜湊插入演算法,這裡需要注意的是,你的演算法中插入的元素一定要是通用元素,所以對於 C++ 或者 Java 語言,一定要使用模板這一類引數作為雜湊插入演算法的物件。然後,就是雜湊表中多個元素衝突時,某個位置的元素使用連結串列往後穿成一串的方案。最終考察 linux 下 malloc(下面的ptmalloc) 函式在頻繁呼叫造成的記憶體碎片問題,以及開源方案解決方案 tcmalloc 和 jemalloc。總體下來,面試官是一步步引導你深入。(有興趣的讀者可以自行搜尋,網上有很多相關資料)

  5. 面試高頻的樹是紅黑樹,也有一部分是B樹(B+樹)。

    紅黑樹一般的問的深淺不一,大多數面試官只要能說出紅黑樹的概念、左旋右旋的方式、分析出查詢和插入的平均演算法複雜度和最好最壞時的演算法複雜度,並不要寫面試者寫出具體程式碼實現。一般 C++ 面試問 stl 的map,java 面試問 TreeMap 基本上就等於開始問你紅黑樹了,要有心裡準備。筆者曾經面試愛奇藝被問過紅黑樹。

    B樹一般不會直接問,問的最多的形式是通過問 MySQL 索引實現原理(資料庫知識點將在下文中討論)。筆者面試騰訊看點部門二面被問到過。

  6. 圖的問題就我個人面試從來沒遇到過,不過據我某位哥哥所說,他在進三星電子之前有一道面試題就是深度優先廣度優先問題。

  7. 其他的一些演算法

    如A*尋路、霍夫曼編碼也偶爾會在某一個領域的公司的面試中被問到,如寶開(《植物大戰殭屍》的母公司, 在上海人民廣場附近有分公司)。

編碼基本功

還有一類面試題不好分類,筆者姑且將其當作是考察編碼基本功,這類問題既可以考察演算法也可以考察你寫程式碼基本素養,這些素養不僅包括編碼風格、計算機英語水平、除錯能力等,還包括你對細節的掌握和易錯點理解,如有意識地對邊界條件的檢查和非法值的過濾。請讀者看以下的程式碼執行結果是什麼?(筆者2011年去北京中關村的鼎普面試的問題)

for(char i = 0; i 256; ++i) 
{ 
    printf("%d\n", i);
}

下面再列舉幾個常見的編碼題:

  1. 實現一個 memmov 函式

    這個題目考查點在於 memmov 函式與 memcpy 函式的區別,這兩者對於源地址與目標地址記憶體有重疊的這一情況的處理方式是不一樣的。

  2. 實現strcpy或strncpy函式

    這個函式寫出來沒啥難度,但是除了邊界條件需要檢查以外,還有一個容易被忽視的地方即其返回值一定要是目標記憶體地址,以支援所謂的鏈式拷貝。即:

    strcpy(dest3, strcpy(dest2, strcpy(dest1, src1)));
    
  3. 實現atoi函式

    這個函式的簽名如下:

    int atoi(const char* p);
    

    容易疏忽的地方有如下幾點:

  4. 小數點問題,如數字0.123和.123都是合法的;

  5. 正負號問題,如+123和-123;

  6. 考慮如何識別第一個非法字元問題,如123Z89,則應轉換成應該123。

    我在面試掌門科技(無線萬能鑰匙那一家)就遇到過這樣的問題。

多執行緒開發基礎

現如今的多核CPU早已經是司空見慣,而多執行緒程式設計早已經是“飛入尋常百姓家”。對於大多數桌面應用(與 Web 開發相對),尤其是像後臺開發這樣的崗位,且面試者是社會人員(有一定的工作經驗),如果面試者不熟悉多執行緒程式設計,那麼一般會被直接 pass 掉。

這裡說的“熟悉多執行緒程式設計”到底熟悉到什麼程度呢?一般包括:知道何種場合下需要新建新的執行緒、執行緒如何建立和等待、執行緒與程序的關係、執行緒區域性儲存(TLS 或者叫 thread local)、多執行緒訪問資源產生競態的原因和解決方案等等、熟練使用所在作業系統平臺提供的執行緒同步的各種原語。

對於 C++ 開發者,你需要:

  • 對於 Windows 開發者,你需要熟練使用 Interlock系列函式、CriticalSection、Event、Mutex、Semphore等API 函式和兩個重要的函式 WaitForSingleObject、WaitForMultipleObjects。

  • 對於linux 開發者,你需要熟練使用 mutex、semphore、condition_variable、read-write-lock 等作業系統API。

對於 Java,你需要熟悉使用 synchronized關鍵字、CountDownLatch、CyclicBarrier、Semaphore以及java.util.concurrent 等包中的大多數執行緒同步物件。

資料庫

資料庫知識一般在大的網際網路企業對應屆生不做硬性要求,對於小的網際網路企業或社會人士一般有一定的要求。其要求一般包括:

  1. 熟悉基本 SQL 操作

    包括增刪改查(insert、delete、update、select語句),排序 order,條件查詢(where 子語句),限制查詢結果數量(LIMIT語句)等

  2. 稍微高階一點的 SQL 操作(如Group by,in,join,left join,多表聯合查詢,別名的使用,select 子語句等)

  3. 索引的概念、索引的原理、索引的建立技巧

  4. 資料庫本身的操作,建庫建表,資料的匯入匯出

  5. 資料庫使用者許可權控制(許可權機制)

  6. MySQL的兩種資料庫引擎的區別

  7. SQL 優化技巧

網路程式設計

網路程式設計這一塊,對於應屆生或者初級崗位一般只會問一些基礎網路通訊原理(如三次握手和四次揮手)的socket 基礎 API 的使用,客戶端與伺服器端網路通訊的流程(回答 【客戶端建立socket -> 連線server ->收發資料;伺服器端建立socket -> 繫結ip和埠號 -> 啟動偵聽 ->接受客戶端連線 ->與客戶端通訊收發資料】即可)、TCP 與 UDP的區別等等。

對於工作經驗三年以內的社會人士或者一些中級面試者一般會問一些稍微重難點問題,如 select 函式的用法,非阻塞 connect 函式的寫法,epoll 的水平和邊緣模式、阻塞socket與非阻塞socket的區別、send/recv函式的返回值情形、reuse_addr選項等等。Windows 平臺可能還會問 WSAEventSelect 和 WSAAsyncSelect 函式的用法、完成埠(IOCP模型)。

對於三年以上尤其是“號稱”自己設計過伺服器、看過開源網路通訊庫程式碼的面試者,面試官一般會深入問一些問題,這類問題要麼是實際專案中常見的難題或者網路通訊細節,根據我的經驗,一般有這樣一些問題:

  1. nagle演算法;

  2. keepalive選項;

  3. Linger選項;

  4. 對於某一端出現大量CLOSE_WAIT 或者 TIME_WAIT如何解決;

  5. 通訊協議如何設計或如何解決資料包的粘包與分片問題;

  6. 心跳機制如何設計;(可能不會直接問問題本身,如問如何檢查死鏈)

  7. 斷線重連機制如何設計;

  8. 對 IO Multiplexing 技術的理解;

  9. 收發資料包正確的方式,收發緩衝區如何設計;

  10. 優雅關閉;

  11. 定時器如何設計;

  12. epoll 的實現原理。

舉個例子,讓讀者感受一下,筆者曾去BiliBili被問過這樣一個問題:如果A機器與B機器網路 connect 成功後從未互發過資料,此時其中一機器突然斷電,則另外一臺機器與斷電的機器之間的網路連線處於哪種狀態?

記憶體資料庫技術

時下以NoSql key-value為思想的記憶體資料庫大行其道,廣泛地用於各種後臺專案開發。所以熟悉一種或幾種記憶體資料庫程式已經是面試後臺開發的基本要求,而這當中以 redis 和 memcached 為最典型代表,這裡以 redis 為例。

  • 第一層面一般是對 redis 的基礎用法的考察

    如考察 redis 支援的基礎資料型別、redis的資料持久化、事務等。

  • 第二層面不僅考察 redis 的基礎用法,還會深入到 redis 原始碼層面上,如 redis 的網路通訊模型、redis 各種資料結構的實現等等。

筆者以為,無論是從找工作應付面試還是從提高技術的角度,redis 是一個非常值得學習的開源軟體,希望廣大讀者有意識地去了解、學習它。

專案經驗

除了社會招聘和一些小型的企業,一般的大型網際網路公司對應屆生不會做過多的專案經驗要求,而是希望他們演算法與資料結構等基礎紮實、動手實踐能力強即可。對於一般的小公司,對於應屆生會要求其至少熟練使用一門程式語言以及相應的開發工具,號稱熟悉linux C++ 開發的面試者,不熟悉 GDB 除錯基本上不是真正的熟悉 linux C++ 開發;號稱熟悉彙編或者反彙編,不熟悉 IDA 或者 OllyDbg,基本上也是名不符實的;號稱熟悉 VC++ 開發,連F8、F9、F10、F11、F12等快捷鍵不熟悉也是難以經得住面試官的提問的;號稱熟悉 Java 開發的卻對 IDEA 或 eclipse 陌生,這也是說不過去的。

這裡給一些學歷不算好,學校不是非常有名,尤其是二本以下的廣大想進入 IT 行業的同學一個建議,在大學期間除了要學好計算機專業基礎知識以外,一定要熟練使用一門程式語言以及相應的開發工具。

關於專案經驗,許多面試者認為一定要是自己參與的專案,其實也可以來源於你學習和閱讀他人原始碼或開源軟體的原始碼,如果你能理解並掌握這些開源軟體中的思想和技術,在面試的時候能夠與面試官侃侃而談,面試官也會非常滿意的。筆者的一個學弟前段時間告訴我,他看懂了我公眾號【easyserverdev】中《伺服器開發基礎系列和進階》的文章後,成功拿到了網易的offer,有興趣的讀者可以好好看一下。

很多同學可能糾結大學或者研究生期間要不要跟著導師做一些專案。當然,如果這些專案是課程要求,那麼你必須得參加;如果這些專案是可以選擇性的,尤其是一些僅僅拿著第三方的庫進行所謂的包裝和加工,那麼建議可以少參加一些。

思路總結

不知道通過我上面的技術分析,聰明的讀者是否已經明確本文開頭“成都-go-戒炸雞”同學提出的面試題中,哪些是技術面試重難點,哪些又是技術開發的重難點呢?

技術比重與薪資

這裡根據我自己招人的經驗來談一談技術水平與薪資,就上面的面試題來看:

  • 第一層次:如果面試者能答出上面面試題中的C++基礎問題和演算法與資料結構題目(如 C++ 函式與hash衝突的解決、innodb索引原理,單向圖最短路徑,動態規劃演算法等),可以認為面試者是一個合格的初、中級開發者,薪資範圍一般在6 ~ 12k(注意:這裡以我所在的上海為參考標準)。

  • 第二層次:在第一層次基礎之上,如果面試者還能答出上述面試題中網路程式設計相關的或者多執行緒相關的問題(如socket函式select的缺陷,epoll模型,同步互斥,非同步非阻塞,回撥的概念等),可以認為面試者是個基礎不錯的中級開發者,薪資範圍一般在14~22k之間。

  • 第三層次:在前兩個層次之間,如果面試者還能回答出上述問題中關於redis、memcached和mq實現原理,說明面試者是一個有著不錯專案經驗並且對一些常用開源專案也有一定的理解,薪資可以給到22k +。

總結

工資收入是每個人的祕密,一般不輕易對外人道也。這裡筆者冒天下之大不韙,只想說明一點——對於普通開發人員,提高薪資最好的捷徑就是提高自己的技術,無論是“面向搜尋引擎程式設計”還是“面向工資程式設計”終將得不償失,聰明的你一定會深謀遠慮的。

文章來源:https://mp.weixin.qq.com/s?__biz=MzU2MTQ1MzI3NQ==&mid=2247486424&idx=1&sn=ad30fe4ed875023735a1d01e30086e93&chksm=fc79ca6ccb0e437a0d32eba037b58cdd2ccff4344f516b2aa13baee6002bd34db0285cd9a124&token=1887257674&lang=zh_CN#rd

作者公眾號:高效能伺服器開發