2022秋招面经(C++软开)
目錄
- 1. 語言基礎 (C/C++)
- (0) 指針和引用的區別
- (1)在函數參數傳遞的時候,什么時候使用指針,什么時候使用引用?
- (2) 堆和棧有什么區別
- (3)堆快一點還是棧快一點?(字節提前批一面)
- (4) new和delete是如何實現的,new 與 malloc的異同處
- (5)既然有了malloc/free,C++中為什么還需要new/delete呢?
- (6) C和C\+\+的區別
- (7)delete和delete\[\]的區別
- (8) C++、Java的聯系與區別,包括語言特性、垃圾回收、應用場景等(java的垃圾回收機制)
- (9)C++和python的區別
- (10) Struct和class的區別
- (11) define 和const的聯系與區別(編譯階段、安全性、內存占用等)
- (12) 在C\+\+中const的用法(定義,用途)
- (13) C++中的static用法和意義
- (14) 計算下面幾個類的大小:
- (15) C++的STL介紹(這個系列也很重要,建議侯捷老師的這方面的書籍與視頻),其中包括內存管理allocator,函數,實現機理,多線程實現等
- (16) STL源碼中的hash表的實現
- (17)解決哈希沖突的方式?
- (18) STL中unordered_map和map的區別
- (19) STL中vector的實現
- (20) vector使用的注意點及其原因,頻繁對vector調用push_back\(\)對性能的影響和原因。
- (21)C++中vector和list的區別
- (22) C++中的重載和重寫的區別:
- (23) C ++內存管理(熱門問題)
- (24) 介紹面向對象的三大特性,并且舉例說明每一個。
- (25) 多態的實現(和下個問題一起回答)
- (26) C++虛函數相關(虛函數表,虛函數指針),虛函數的實現原理(熱門,重要)
- (27) 實現編譯器處理虛函數表應該如何處理
- (28) 基類的析構函數一般寫成虛函數的原因
- (29) 構造函數為什么一般不定義為虛函數
- (30) 構造函數或者析構函數中調用虛函數會怎樣
- (31) 純虛函數
- (32) 靜態綁定和動態綁定的介紹
- (33) 深拷貝和淺拷貝的區別(舉例說明深拷貝的安全性)
- (34) 對象復用的了解,零拷貝的了解
- (35) 介紹C++所有的構造函數
- (36) 什么情況下會調用拷貝構造函數(三種情況)
- (37) 結構體內存對齊方式和為什么要進行內存對齊?
- (38) 內存泄露的定義,如何檢測與避免?
- (39) C++的智能指針有哪些
- (40) 調試程序的方法
- (41) 遇到coredump要怎么調試
- (42) inline關鍵字說一下 和宏定義有什么區別
- (43) 模板的用法與適用場景 實現原理
- (44) 成員初始化列表的概念,為什么用成員初始化列表會快一些(性能優勢)?
- (45) 用過C11嗎,知道C11新特性嗎?(有面試官建議熟悉C11)
- (46) C++的調用慣例(簡單一點C++函數調用的壓棧過程)
- (47) C++的四種強制轉換
- (48)string的底層實現
- (49)一個函數或者可執行文件的生成過程或者編譯過程是怎樣的
- (50)set,map和vector的插入復雜度
- (51)定義和聲明的區別
- (52)typdef和define區別
- (53)被free回收的內存是立即返還給操作系統嗎?為什么
- (54)引用作為函數參數以及返回值的好處
- (55)友元函數和友元類
- (56) 說一下volatile關鍵字的作用
- (57) STL中的sort()算法是用什么實現的,stable_sort()呢
- (58)vector會迭代器失效嗎?什么情況下會迭代器失效?
- (58)為什么C++沒有實現垃圾回收?
- (59)移動構造函數
- 2. 計網相關
- (1) 建立TCP服務器的各個系統調用
- (2) 繼上一題,說明socket網絡編程有哪些系統調用?其中close是一次就能直接關閉的嗎,半關閉狀態是怎么產生的?
- (3) 對路由協議的了解與介紹。內部網關協議IGP包括RIP,OSPF,和外部網關協議EGP和BGP.
- (4) UDP如何實現可靠傳輸
- (5) TCP和UDP的區別
- (6) TCP和UDP相關的協議與端口號
- (7) TCP(UDP,IP)等首部的認識(http請求報文構成)
- (8) 網頁解析的過程與實現方法
- (9) 在瀏覽器中輸入URL后執行的全部過程(如www.baidu.com)
- (10) 網絡層分片的原因與具體實現
- (11) TCP的三次握手與四次揮手的詳細介紹(TCP連接建立與斷開是熱門問題)
- (12) TCP握手以及每一次握手客戶端和服務器端處于哪個狀態
- (13) 為什么使用三次握手,兩次握手可不可以?
- (14) TIME_WAIT的意義(為什么要等于2MSL)
- (15) 超時重傳機制(不太高頻)
- (16) TCP怎么保證可靠性?
- (17) 流量控制的介紹,采用滑動窗口會有什么問題(死鎖可能,糊涂窗口綜合征)?
- (18) tcp滑動窗口協議
- (19) 擁塞控制和流量控制的區別
- (20) TCP擁塞控制,算法名字?(極其重要)
- (21) http協議與TCP的區別與聯系
- (22) http/1.0和http/1.1 和 http/2.0的區別
- (23) http的請求方法有哪些?get和post的區別。
- (24) http的狀態碼 403 201等等是什么意思
- (25) http和https的區別,由http升級為https需要做哪些操作
- (26) https的具體實現,怎么確保安全性
- (27) TCP三次握手時的第一次的seq序號是怎樣產生的
- (28) 一個機器能夠使用的端口號上限是多少,為什么?可以改變嗎?那如果想要用的端口超過這個限制怎么辦?
- (29) 對稱密碼和非對稱密碼體系
- (30) 數字證書的了解(高頻)
- (31) 服務器出現大量close_wait的連接的原因以及解決方法
- (32) 消息摘要算法列舉一下,介紹MD5算法,為什么MD5是不可逆的,有什么辦法可以加強消息摘要算法的安全性讓它不那么容易被破解呢?(百度安全一面)
- (33) 單條記錄高并發訪問的優化
- (34) 介紹一下ping的過程,分別用到了哪些協議
- (35) TCP/IP的粘包與避免介紹一下
- (36) 說一下TCP的封包和拆包
- (37) 一個ip配置多個域名,靠什么識別?
- (38) 服務器攻擊(DDos攻擊)
- (39)DNS的工作過程和原理
- (41)OSA七層協議和五層協議,分別有哪些
- (42)IP尋址和MAC尋址有什么不同,怎么實現的
- (43)ARP協議
- 3. 數據庫
- (1) 關系型和非關系型數據庫的區別(低頻)
- (2) 什么是非關系型數據庫(低頻)
- (3) 說一下 MySQL 執行一條查詢語句的內部執行過程?
- (4) 數據庫的索引類型
- (5) 說一下事務是怎么實現的
- (6) MySQL怎么建立索引,怎么建立主鍵索引,怎么刪除索引?
- (7) 索引的優缺點,什么時候使用索引,什么時候不能使用索引(重點)
- (8) 索引的底層實現(重點)
- (9) B樹和B+樹的區別(重點)
- (10) 索引最左前綴/最左匹配
- (11) Mysql的優化(高頻,索引優化,性能優化)
- (12) MYSQL數據庫引擎介紹,innodb和myisam的特點與區別
- (13) 數據庫中事務的ACID(四大特性都要能夠舉例說明,理解透徹,比如原子性和一致性的關聯,隔離性不好會出現的問題)
- (14)什么是臟讀,不可重復讀和幻讀?
- (15) 數據庫的隔離級別,mysql和Oracle的隔離級別分別是什么(重點)
- (16) 數據庫連接池的作用
- (17) Mysql的表空間方式,各自特點
- (18) 分布式事務
- (19) 數據庫的范式
- (20) 數據的鎖的種類,加鎖的方式
- (21) 什么是共享鎖和排他鎖
- (22) 分庫分表的理解和簡介
- (23) MySQL常用的存儲引擎
- (24)數據庫高并發的解決方案
- (25)樂觀鎖與悲觀鎖解釋一下
- (26)樂觀鎖與悲觀鎖是怎么實現的
- (27)對數據庫目前最新技術有什么了解嗎
- 4. Linux
- (0) Linux常用命令操作
- (1) Linux的I/O模型介紹以及同步異步阻塞非阻塞的區別(超級重要)
- (2) 文件系統的理解(EXT4,XFS,BTRFS)
- (3) EPOLL的介紹和了解
- (4) IO復用的三種方法(select,poll,epoll)深入理解,包括三者區別,內部原理實現?
- (5) Epoll的ET模式和LT模式(ET的非阻塞)
- (6) 查詢進程占用CPU的命令(注意要了解到used,buf,代表意義)
- (7) linux的其他常見命令(kill,find,cp等等)
- (8) shell腳本用法
- (9) 硬連接和軟連接的區別
- (10) 文件權限怎么看(rwx)
- (11) 文件的三種時間(mtime, atime,ctime),分別在什么時候會改變
- (12) Linux監控網絡帶寬的命令,查看特定進程的占用網絡資源情況命令
- (13)Linux中線程的同步方式有哪些?
- (14)怎么修改一個文件的權限
- (15)查看文件內容常用命令
- (16)怎么找出含有關鍵字的前后4行
- (17)Linux的GDB調試
- (18)coredump是什么 怎么才能coredump
- (19)tcpdump常用命令
- (20) crontab命令
- (21) 查看后臺進程
- 5. 操作系統
- (1) 進程與線程的區別和聯系(重點)
- (2) Linux理論上最多可以創建多少個進程?一個進程可以創建多少線程,和什么有關
- (3) 馮諾依曼結構有哪幾個模塊?分別對應現代計算機的哪幾個部分?(百度安全一面)
- (4) 進程之間的通信方法有哪幾種 (重點)
- (5) 進程調度方法詳細介紹
- (6) 進程的執行過程是什么樣的,執行一個進程需要做哪些工作?
- (6) 操作系統的內存管理說一下
- (7) 實現一個LRU算法
- (8) 死鎖產生的必要條件(怎么檢測死鎖,解決死鎖問題)
- (8) 死鎖的恢復
- (8)什么是饑餓
- (9) 如果要你實現一個mutex互斥鎖你要怎么實現?
- (10)線程之間的通信方式有哪些? 進程與線程同步?
- (13) 什么時候用多進程,什么時候用多線程
- (14) 文件讀寫使用的系統調用
- (15) 孤兒進程和僵尸進程分別是什么,怎么形成的?
- (16) 說一下PCB/說一下進程地址空間/
- (17) 內核空間和用戶空間是怎樣區分的
- (18) 多線程是如何同步的(尤其是如果項目中用到了多線程,很大可能會結合討論)
- (19) 同一個進程內的線程會共享什么資源?
- (20) 異常和中斷的區別
- (21) 一般情況下在Linux/windows平臺下棧空間的大小
- (22)虛擬內存的了解
- (23)服務器高并發的解決方案
- (24)協程了解嗎(高頻)
- (25)那協程的底層是怎么實現的,怎么使用協程?
- (26)進程的狀態以及轉換圖
- (27)在執行malloc申請內存的時候,操作系統是怎么做的?/內存分配的原理說一下/malloc函數底層是怎么實現的?/進程是怎么分配內存的?
- (28)什么是字節序?怎么判斷是大端還是小端?有什么用?
- 6. 場景題/算法題
- (0) leetcode hot100至少刷兩遍,劍指offer至少刷兩遍 重中之重!!
- (1) 介紹熟悉的設計模式(單例,簡單工廠模式)
- (2) 寫單例模式,線程安全版本
- (3) 寫三個線程交替打印ABC
- (4) 二維碼登錄的實現過程 場景題
- (5) 不使用臨時變量實現swap函數
- (6) 實現一個strcpy函數(或者memcpy),如果內存可能重疊呢
- (7) 實現快排
- (8) 實現一個堆排序
- (8) 實現一個插入排序
- (9) 快排存在的問題,如何優化
- (10) 反轉一個鏈表(招銀網絡二面)
- (11) Top K問題(可以采取的方法有哪些,各自優點?)(重點)
- (12) 8G的int型數據,計算機的內存只有2G,怎么對它進行排序?(外部排序)(百度一面)
- (13) 自己構建一棵二叉樹,使用帶有null標記的前序遍歷序列
- (14) 介紹一下b樹和它的應用場景有哪些
- (15) 介紹一下b+樹和它的應用場景有哪些
- (16) 介紹一下紅黑樹和它的應用場景有哪些
- (17) 怎么寫sql取表的前1000行數據(招銀網絡二面)
- (18) N個骰子出現和為m的概率
- (19) 海量數據問題(可參考左神的書)
- (20) 一致性哈希
- (21)希爾排序說一下/手撕
- (22)Dijkstra算法說一下
- (23)實現一個動態數組要怎么實現,說思路(騰訊teg一面)
- (24)最小生成樹算法說一下
- (25) 海量數據的bitmap使用原理
- (26) 布隆過濾器原理與優點
- (27) 布隆過濾器處理大規模問題時的持久化,包括內存大小受限、磁盤換入換出問題
- (28)實現一個隊列,并且使它支持多線程,隊列有什么應用場景(阿里三面)
- 7. 智力題
- (1) 100層樓,只有2個雞蛋,想要判斷出那一層剛好讓雞蛋碎掉,給出策略(滴滴筆試中兩個鐵球跟這個是一類題)
- (2) 毒藥問題,1000瓶水,其中有一瓶可以無限稀釋的毒藥,要快速找出哪一瓶有毒,需要幾只小白鼠
- (3)
- (4) 先手必勝策略問題:100本書,每次能夠拿1-5本,怎么拿能保證最后一次是你拿
- (5) 放n只螞蟻在一條樹枝上,螞蟻與螞蟻之間碰到就各自往反方向走,問總距離或者時間。
- (6) 瓶子換飲料問題:1000瓶飲料,3個空瓶子能夠換1瓶飲料,問最多能喝幾瓶
- (7)在24小時里面時針分針秒針可以重合幾次
- (8) 有一個天平,九個砝碼,一個輕一些,用天平至少幾次能找到輕的?
- (9) 有十組砝碼每組十個,每個砝碼重10g,其中一組每個只有9g,有能顯示克數的秤最少幾次能找到輕的那一組砝碼?
- (10)生成隨機數問題:給定生成1到5的隨機數Rand5(),如何得到生成1到7的隨機數函數Rand7()?
- 賽馬:有25匹馬,每場比賽只能賽5匹,至少要賽多少場才能找到最快的3匹馬?
- 燒 香/繩子/其他 確定時間問題:有兩根不均勻的香,燃燒完都需要一個小時,問怎么確定15分鐘的時長?
- 掰巧克力問題 N*M塊巧克力,每次掰一塊的一行或一列,掰成1*1的巧克力需要多少次?(1000個人參加辯論賽,1V1,輸了就退出,需要安排多少場比賽)
- 8. 測試
- 1. App測試和Web測試的區別
- 2. 設計用例的方法、依據有那些
- 3. 軟件測試的流程
- 4. Android中造成APP閃退的原因總結
- 5. 網頁很卡的原因
- 6. 單元測試、集成測試、系統測試 區別
- 7. 如何對登錄界面進行測試
- 8. 微信紅包功能測試
- 9. HR面
- 1. 自我介紹
- 2. 項目中遇到的最大難點
- 3. 項目中的收獲
- 4. 可以實習的時間,實習時長
- 5. 哪里人
- 6. 說一下自己的性格
- 7. 你的優缺點是什么
- 8. 有什么興趣愛好,畫的怎么樣/球打的如何/游戲打的怎么樣
- 9. 看過最好的一本書是什么
- 10. 學習技術中有什么難點
- 11. 怎么看待加班
- 12. 覺得深圳怎么樣(或者其他地點)
- 13. 遇見過最大的挫折是什么,怎么解決的
- 14. 職業規劃
- 15. 目前的offer情況
- 16. 你最大的優勢和劣勢是什么
- 17. 介紹在項目里面充當的角色
- 18. 介紹一下本科獲得的全國賽獎項的情況
- 19. 最有成就感的事情/最驕傲的一件事情
- 20. 在實驗室中擔任什么角色,參加的XXX能聊聊嗎
- 22. 用兩個詞來形容自己
- 23. 反問
1. 語言基礎 (C/C++)
(0) 指針和引用的區別
- 指針是一個新的變量,指向另一個變量的地址,我們可以通過訪問這個地址來修改另一個變量;而引用是一個別名,對引用的操作就是對變量的本身進行操作
- 指針可以有多級,引用只有一級
- 傳參的時候,使用指針的話需要解引用才能對參數進行修改,而使用引用可以直接對參數進行修改
- 指針的大小一般是4個字節,引用的大小取決于被引用對象的大小
- 指針可以為空,引用不可以。
(1)在函數參數傳遞的時候,什么時候使用指針,什么時候使用引用?
- 需要返回函數內局部變量的內存的時候用指針。使用指針傳參需要開辟內存,用完要記得釋放指針,不然會內存泄漏。而返回局部變量的引用是沒有意義的
- 對棧空間大小比較敏感(比如遞歸)的時候使用引用。使用引用傳遞不需要創建臨時變量,開銷要更小
- 類對象作為參數傳遞的時候使用引用,這是C++類對象傳遞的標準方式
(2) 堆和棧有什么區別
- 從定義上:堆是由new和malloc開辟的一塊內存,由程序員手動管理,棧是編譯器自動管理的內存,存放函數的參數和局部變量。
- 堆空間因為會有頻繁的分配釋放操作,會產生內存碎片
- 堆的生長空間向上,地址越來越大,棧的生長空間向下,地址越來越小
(3)堆快一點還是棧快一點?(字節提前批一面)
棧快一點。因為操作系統會在底層對棧提供支持,會分配專門的寄存器存放棧的地址,棧的入棧出棧操作也十分簡單,并且有專門的指令執行,所以棧的效率比較高也比較快。而堆的操作是由C/C++函數庫提供的,在分配堆內存的時候需要一定的算法尋找合適大小的內存。并且獲取堆的內容需要兩次訪問,第一次訪問指針,第二次根據指針保存的地址訪問內存,因此堆比較慢。
(4) new和delete是如何實現的,new 與 malloc的異同處
在new一個對象的時候,首先會調用malloc為對象分配內存空間,然后調用對象的構造函數。delete會調用對象的析構函數,然后調用free回收內存。
new與malloc都會分配空間,但是new還會調用對象的構造函數進行初始化,malloc需要給定空間大小,而new只需要對象名
(5)既然有了malloc/free,C++中為什么還需要new/delete呢?
https://blog.csdn.net/leikun153/article/details/80612130
- malloc/free和new/delete都是用來申請內存和回收內存的。
- 在對非基本數據類型的對象使用的時候,對象創建的時候還需要執行構造函數,銷毀的時候要執行析構函數。而malloc/free是庫函數,是已經編譯的代碼,所以不能把構造函數和析構函數的功能強加給malloc/free。
(6) C和C++的區別
包括但不限于:
- C是面向過程的語言,C++是面向對象的語言,C++有“封裝,繼承和多態”的特性。封裝隱藏了實現細節,使得代碼模塊化。繼承通過子類繼承父類的方法和屬性,實現了代碼重用。多態則是“一個接口,多個實現”,通過子類重寫父類的虛函數,實現了接口重用。
- C和C++內存管理的方法不一樣,C使用malloc/free,C++除此之外還用new/delete
- C++中還有函數重載和引用等概念,C中沒有
(7)delete和delete[]的區別
-
delete只會調用一次析構函數,而delete[]會調用每個成員的析構函數
-
用new分配的內存用delete釋放,用new[]分配的內存用delete[]釋放
(8) C++、Java的聯系與區別,包括語言特性、垃圾回收、應用場景等(java的垃圾回收機制)
包括但不限于:
- C++ 和Java都是面向對象的語言,C++是編譯成可執行文件直接運行的,JAVA是編譯之后在JAVA虛擬機上運行的,因此JAVA有良好的跨平臺特性,但是執行效率沒有C++ 高。
- C++的內存管理由程序員手動管理,JAVA的內存管理是由Java虛擬機完成的,它的垃圾回收使用的是標記-回收算法
- C++有指針,Java沒有指針,只有引用
- JAVA和C++都有構造函數,但是C++有析構函數但是Java沒有
(9)C++和python的區別
包括但不限于:
(10) Struct和class的區別
- 使用struct時,它的成員的訪問權限默認是public的,而class的成員默認是private的
- struct的繼承默認是public繼承,而class的繼承默認是private繼承
- class可以用作模板,而struct不能
(11) define 和const的聯系與區別(編譯階段、安全性、內存占用等)
聯系:它們都是定義常量的一種方法。
區別:
- define定義的常量沒有類型,只是進行了簡單的替換,可能會有多個拷貝,占用的內存空間大,const定義的常量是有類型的,存放在靜態存儲區,只有一個拷貝,占用的內存空間小。
- define定義的常量是在預處理階段進行替換,而const在編譯階段確定它的值。
- define不會進行類型安全檢查,而const會進行類型安全檢查,安全性更高。
- const可以定義函數而define不可以。
(12) 在C++中const的用法(定義,用途)
- const修飾類的成員變量時,表示常量不能被修改
- const修飾類的成員函數,表示該函數不會修改類中的數據成員,不會調用其他非const的成員函數
(13) C++中的static用法和意義
static的意思是靜態的,可以用來修飾變量,函數和類成員。
-
變量:被static修飾的變量就是靜態變量,它會在程序運行過程中一直存在,會被放在靜態存儲區。局部靜態變量的作用域在函數體中,全局靜態變量的作用域在這個文件里。
-
函數:被static修飾的函數就是靜態函數,靜態函數只能在本文件中使用,不能被其他文件調用,也不會和其他文件中的同名函數沖突。
-
類:而在類中,被static修飾的成員變量是類靜態成員,這個靜態成員會被類的多個對象共用。被static修飾的成員函數也屬于靜態成員,不是屬于某個對象的,訪問這個靜態函數不需要引用對象名,而是通過引用類名來訪問。
【note】靜態成員函數要訪問非靜態成員時,要用過對象來引用。局部靜態變量在函數調用結束后也不會被回收,會一直在程序內存中,直到該函數再次被調用,它的值還是保持上一次調用結束后的值。
注意和const的區別。const強調值不能被修改,而static強調唯一的拷貝,對所有類的對象都共用。
(14) 計算下面幾個類的大小:
class A {}; int main(){cout<<sizeof(A)<<endl;// 輸出 1;A a; cout<<sizeof(a)<<endl;// 輸出 1;return 0; }空類的大小是1, 在C++中空類會占一個字節,這是為了讓對象的實例能夠相互區別。具體來說,空類同樣可以被實例化,并且每個實例在內存中都有獨一無二的地址,因此,編譯器會給空類隱含加上一個字節,這樣空類實例化之后就會擁有獨一無二的內存地址。當該空白類作為基類時,該類的大小就優化為0了,子類的大小就是子類本身的大小。這就是所謂的空白基類最優化。
空類的實例大小就是類的大小,所以sizeof(a)=1字節,如果a是指針,則sizeof(a)就是指針的大小,即4字節。
class A { virtual Fun(){} }; int main(){cout<<sizeof(A)<<endl;// 輸出 4(32位機器)/8(64位機器);A a; cout<<sizeof(a)<<endl;// 輸出 4(32位機器)/8(64位機器);return 0; }因為有虛函數的類對象中都有一個虛函數表指針 __vptr,其大小是4字節
靜態成員存放在靜態存儲區,不占用類的大小, 普通函數也不占用類大小
class A { int a; }; int main(){cout<<sizeof(A)<<endl;// 輸出 4;A a; cout<<sizeof(a)<<endl;// 輸出 4;return 0; } class A { static int a; int b; };; int main(){cout<<sizeof(A)<<endl;// 輸出 4;A a; cout<<sizeof(a)<<endl;// 輸出 4;return 0; }靜態成員a不占用類的大小,所以類的大小就是b變量的大小 即4個字節
(15) C++的STL介紹(這個系列也很重要,建議侯捷老師的這方面的書籍與視頻),其中包括內存管理allocator,函數,實現機理,多線程實現等
C++ STL從廣義來講包括了三類:算法,容器和迭代器。
- 算法包括排序,復制等常用算法,以及不同容器特定的算法。
- 容器就是數據的存放形式,包括序列式容器和關聯式容器,序列式容器就是list,vector等,關聯式容器就是set,map等。
- 迭代器就是在不暴露容器內部結構的情況下對容器的遍歷。
(16) STL源碼中的hash表的實現
STL中的hash表就unordered_map。使用的是哈希進行實現(注意與map的區別)。它記錄的鍵是元素的哈希值,通過對比元素的哈希值來確定元素的值。
unordered_map的底層實現是hashtable,采用開鏈法(也就是用桶)來解決哈希沖突,當桶的大小超過8時,就自動轉為紅黑樹進行組織。
(17)解決哈希沖突的方式?
(18) STL中unordered_map和map的區別
- unordered_map是使用哈希實現的,占用內存比較多,查詢速度比較快,是常數時間復雜度。它內部是無序的,需要實現==操作符。
- map底層是采用紅黑樹實現的,插入刪除查詢時間復雜度都是O(log(n)),它的內部是有序的,因此需要實現比較操作符(<)。
(19) STL中vector的實現
STL中的vector是封裝了動態數組的順序容器。不過與動態數組不同的是,vector可以根據需要自動擴大容器的大小。具體策略是每次容量不夠用時重新申請一塊大小為原來容量兩倍的內存,將原容器的元素拷貝至新容器,并釋放原空間,返回新空間的指針。
在原來空間不夠存儲新值時,每次調用push_back方法都會重新分配新的空間以滿足新數據的添加操作。如果在程序中頻繁進行這種操作,還是比較消耗性能的。
(20) vector使用的注意點及其原因,頻繁對vector調用push_back()對性能的影響和原因。
如果需要頻繁插入,最好先指定vector的大小,因為vector在容器大小不夠用的時候會重新申請一塊大小為原容器兩倍的空間,并將原容器的元素拷貝到新容器中,并釋放原空間,這個過程是十分耗時和耗內存的。頻繁調用push_back()會使得程序花費很多時間在vector擴容上,會變得很慢。這種情況可以考慮使用list。
(21)C++中vector和list的區別
vector和數組類似,擁有一段連續的內存空間。vector申請的是一段連續的內存,當插入新的元素內存不夠時,通常以2倍重新申請更大的一塊內存,將原來的元素拷貝過去,釋放舊空間。因為內存空間是連續的,所以在進行插入和刪除操作時,會造成內存塊的拷貝,時間復雜度為o(n)。
list是由雙向鏈表實現的,因此內存空間是不連續的。只能通過指針訪問數據,所以list的隨機存取非常沒有效率,時間復雜度為o(n); 但由于鏈表的特點,能高效地進行插入和刪除。
vector擁有一段連續的內存空間,能很好的支持隨機存取,因此vector::iterator支持“+”,“+=”,“<”等操作符。
list的內存空間可以是不連續,它不支持隨機訪問,因此list::iterator則不支持“+”、“+=”、“<”等
vector::iterator和list::iterator都重載了“++”運算符。
總之,如果需要高效的隨機存取,而不在乎插入和刪除的效率,使用vector;
如果需要大量的插入和刪除,而不關心隨機存取,則應使用list。
(22) C++中的重載和重寫的區別:
- 重載(overload)是指函數名相同,參數列表不同的函數實現方法。它們的返回值可以不同,但返回值不可以作為區分不同重載函數的標志。
- 重寫(overwide)是指函數名相同,參數列表相同,只有方法體不相同的實現方法。一般用于子類繼承父類時對父類方法的重寫。子類的同名方法屏蔽了父類方法的現象稱為隱藏。
詳見:https://blog.csdn.net/weixin_30379911/article/details/99497160
(23) C ++內存管理(熱門問題)
https://blog.csdn.net/qq_43152052/article/details/98889139
在C++中,內存分成5個區,他們分別是堆、棧、全局/靜態存儲區和常量存儲區和代碼區。
- 棧,在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。
- 堆,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那么在程序結束后,操作系統會自動回收。
- 全局/靜態存儲區,內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。它主要存放靜態數據(局部static變量,全局static變量)、全局變量和常量。
- 常量存儲區,這是一塊比較特殊的存儲區,他們里面存放的是常量字符串,不允許修改。
- 代碼區,存放程序的二進制代碼
關于這個有很多種說法,有的會增加一個自由存儲區,存放malloc分配得到的內存,與堆相似。
(24) 介紹面向對象的三大特性,并且舉例說明每一個。
面向對象的三大特性是:封裝,繼承和多態。
- 封裝隱藏了類的實現細節和成員數據,實現了代碼模塊化,如類里面的private和public;
- 繼承使得子類可以復用父類的成員和方法,實現了代碼重用;
- 多態則是“一個接口,多個實現”,通過父類調用子類的成員,實現了接口重用,如父類的指針指向子類的對象。
(25) 多態的實現(和下個問題一起回答)
C++ 多態包括編譯時多態和運行時多態,編譯時多態體現在函數重載和模板上,運行時多態體現在虛函數上。
- 虛函數:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數.
(26) C++虛函數相關(虛函數表,虛函數指針),虛函數的實現原理(熱門,重要)
C++的虛函數是實現多態的機制。它是通過虛函數表實現的,虛函數表是每個類中存放虛函數地址的指針數組,類的實例在調用函數時會在虛函數表中尋找函數地址進行調用,如果子類覆蓋了父類的函數,則子類的虛函數表會指向子類實現的函數地址,否則指向父類的函數地址。一個類的所有實例都共享同一張虛函數表。
詳見:C++虛函數表剖析
- 如果多重繼承和多繼承的話,子類的虛函數表長什么樣子?
多重繼承的情況下越是祖先的父類的虛函數更靠前,多繼承的情況下越是靠近子類名稱的類的虛函數在虛函數表中更靠前。詳見:https://blog.csdn.net/qq_36359022/article/details/81870219
(27) 實現編譯器處理虛函數表應該如何處理
編譯器處理虛函數的方法是:
如果類中有虛函數,就將虛函數的地址記錄在類的虛函數表中。派生類在繼承基類的時候,如果有重寫基類的虛函數,就將虛函數表中相應的函數指針設置為派生類的函數地址,否則指向基類的函數地址。
為每個類的實例添加一個虛表指針(vptr),虛表指針指向類的虛函數表。實例在調用虛函數的時候,通過這個虛函數表指針找到類中的虛函數表,找到相應的函數進行調用。
詳見:虛函數的作用及其底層實現機制
(28) 基類的析構函數一般寫成虛函數的原因
首先析構函數可以為虛函數,當析構一個指向子類的父類指針時,編譯器可以根據虛函數表尋找到子類的析構函數進行調用,從而正確釋放子類對象的資源。
如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除指向子類的父類指針時,只會調用父類的析構函數而不調用子類析構函數,這樣就會造成子類對象析構不完全造成內存泄漏。
(29) 構造函數為什么一般不定義為虛函數
1)因為創建一個對象時需要確定對象的類型,而虛函數是在運行時確定其類型的。而在構造一個對象時,由于對象還未創建成功,編譯器無法知道對象的實際類型,是類本身還是類的派生類等等
2)虛函數的調用需要虛函數表指針,而該指針存放在對象的內存空間中;若構造函數聲明為虛函數,那么由于對象還未創建,還沒有內存空間,更沒有虛函數表地址用來調用虛函數即構造函數了
(30) 構造函數或者析構函數中調用虛函數會怎樣
在構造函數中調用虛函數,由于當前對象還沒有構造完成,此時調用的虛函數指向的是基類的函數實現方式。
在析構函數中調用虛函數,此時調用的是子類的函數實現方式。
(31) 純虛函數
純虛函數是只有聲明沒有實現的虛函數,是對子類的約束,是接口繼承
包含純虛函數的類是抽象類,它不能被實例化,只有實現了這個純虛函數的子類才能生成對象
使用場景:當這個類本身產生一個實例沒有意義的情況下,把這個類的函數實現為純虛函數,比如動物可以派生出老虎兔子,但是實例化一個動物對象就沒有意義。并且可以規定派生的子類必須重寫某些函數的情況下可以寫成純虛函數。
(32) 靜態綁定和動態綁定的介紹
C++中的靜態綁定和動態綁定
靜態綁定也就是將該對象相關的屬性或函數綁定為它的靜態類型,也就是它在聲明的類型,在編譯的時候就確定。在調用的時候編譯器會尋找它聲明的類型進行訪問。
動態綁定就是將該對象相關的屬性或函數綁定為它的動態類型,具體的屬性或函數在運行期確定,通常通過虛函數實現動態綁定。
(33) 深拷貝和淺拷貝的區別(舉例說明深拷貝的安全性)
淺拷貝就是將對象的指針進行簡單的復制,原對象和副本指向的是相同的資源。
而深拷貝是新開辟一塊空間,將原對象的資源復制到新的空間中,并返回該空間的地址。
深拷貝可以避免重復釋放和寫沖突。例如使用淺拷貝的對象進行釋放后,對原對象的釋放會導致內存泄漏或程序崩潰。
(34) 對象復用的了解,零拷貝的了解
對象復用指得是設計模式,對象可以采用不同的設計模式達到復用的目的,最常見的就是繼承和組合模式了。
零拷貝指的是在進行操作時,避免CPU從一處存儲拷貝到另一處存儲。在Linux中,我們可以減少數據在內核空間和用戶空間的來回拷貝實現,比如通過調用mmap()來代替read調用。
用程序調用mmap(),磁盤上的數據會通過DMA被拷貝的內核緩沖區,接著操作系統會把這段內核緩沖區與應用程序共享,這樣就不需要把內核緩沖區的內容往用戶空間拷貝。應用程序再調用write(),操作系統直接將內核緩沖區的內容拷貝到socket緩沖區中,這一切都發生在內核態,最后,socket緩沖區再把數據發到網卡去。
(35) 介紹C++所有的構造函數
C++中的構造函數主要有三種類型:默認構造函數、重載構造函數和拷貝構造函數
- 默認構造函數是當類沒有實現自己的構造函數時,編譯器默認提供的一個構造函數。
- 重載構造函數也稱為一般構造函數,一個類可以有多個重載構造函數,但是需要參數類型或個數不相同。可以在重載構造函數中自定義類的初始化方式。
- 拷貝構造函數是在發生對象復制的時候調用的。
(36) 什么情況下會調用拷貝構造函數(三種情況)
- 對象以值傳遞的方式傳入函數參數
如 void func(Dog dog){};
- 對象以值傳遞的方式從函數返回
如 Dog func(){ Dog d; return d;}
- 對象需要通過另外一個對象進行初始化
詳見:C++拷貝構造函數詳解
(37) 結構體內存對齊方式和為什么要進行內存對齊?
因為結構體的成員可以有不同的數據類型,所占的大小也不一樣。同時,由于CPU讀取數據是按塊讀取的,內存對齊可以使得CPU一次就可以將所需的數據讀進來。
對齊規則:
- 第一個成員在與結構體變量偏移量為0的地址
- 其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。
- 對齊數=編譯器默認的一個對齊數 與 該成員大小的較小值。
- linux 中默認為4
- vs 中的默認值為8
結構體總大小為最大對齊數的整數倍(每個成員變量除了第一個成員都有一個對齊數)
(38) 內存泄露的定義,如何檢測與避免?
動態分配內存所開辟的空間,在使用完畢后未手動釋放,導致一直占據該內存,即為內存泄漏。
造成內存泄漏的幾種原因:
1)類的構造函數和析構函數中new和delete沒有配套
2)在釋放對象數組時沒有使用delete[],使用了delete
3)沒有將基類的析構函數定義為虛函數,當基類指針指向子類對象時,如果基類的析構函數不是virtual,那么子類的析構函數將不會被調用,子類的資源沒有正確釋放,因此造成內存泄露
4)沒有正確的清楚嵌套的對象指針
避免方法:
(39) C++的智能指針有哪些
C++中的智能指針有auto_ptr,shared_ptr,weak_ptr和unique_ptr。智能指針其實是將指針進行了封裝,可以像普通指針一樣進行使用,同時可以自行進行釋放,避免忘記釋放指針指向的內存地址造成內存泄漏。
- auto_ptr是較早版本的智能指針,在進行指針拷貝和賦值的時候,新指針直接接管舊指針的資源并且將舊指針指向空,但是這種方式在需要訪問舊指針的時候,就會出現問題。
- unique_ptr是auto_ptr的一個改良版,不能賦值也不能拷貝,保證一個對象同一時間只有一個智能指針。
- shared_ptr可以使得一個對象可以有多個智能指針,當這個對象所有的智能指針被銷毀時就會自動進行回收。(內部使用計數機制進行維護)
- weak_ptr是為了協助shared_ptr而出現的。它不能訪問對象,只能觀測shared_ptr的引用計數,防止出現死鎖。
(40) 調試程序的方法
- 通過設置斷點進行調試
- 打印log進行調試
- 打印中間結果進行調試
(41) 遇到coredump要怎么調試
詳解coredump
通常情況下,core文件會包含了程序運行時的內存,寄存器狀態,堆棧指針,內存管理信息還有各種函數調用堆棧信息等,我們可以理解為是程序工作當前狀態存儲生成第一個文件,許多的程序出錯的時候都會產生一個core文件,通過工具分析這個文件,我們可以定位到程序異常退出的時候對應的堆棧調用等信息,找出問題所在并進行及時解決。
core文件默認的存儲位置與對應的可執行程序在同一目錄下,文件名是core,大家可以通過下面的命令看到core文件的存在位置:cat /proc/sys/kernel/core_pattern
- 造成程序coredump的原因有很多,這里總結一些比較常用的經驗吧:
內存訪問越界
a) 由于使用錯誤的下標,導致數組訪問越界。
b) 搜索字符串時,依靠字符串結束符來判斷字符串是否結束,但是字符串沒有正常的使用結束符。
c) 使用strcpy, strcat, sprintf,
strcmp,strcasecmp等字符串操作函數,將目標字符串讀/寫爆。應該使用strncpy, strlcpy, strncat,
strlcat, snprintf, strncmp, strncasecmp等函數防止讀寫越界。
多線程程序使用了線程不安全的函數。
多線程讀寫的數據未加鎖保護。
對于會被多個線程同時訪問的全局數據,應該注意加鎖保護,否則很容易造成coredump
非法指針
a) 使用空指針
b) 隨意使用指針轉換。一個指向一段內存的指針,除非確定這段內存原先就分配為某種結構或類型,或者這種結構或類型的數組,否則不要將它轉換為這種結構或類型的指針,而應該將這段內存拷貝到一個這種結構或類型中,再訪問這個結構或類型。這是因為如果這段內存的開始地址不是按照這種結構或類型對齊的,那么訪問它時就很容易因為bus error而core dump。
堆棧溢出
不要使用大的局部變量(因為局部變量都分配在棧上),這樣容易造成堆棧溢出,破壞系統的棧和堆結構,導致出現莫名其妙的錯誤。
- 使用gdb命令對core文件進行調試
以下例子在Linux上編寫一段代碼并導致segment fault 并產生core文件
mkdir coredumpTest vim coredumpTest.cpp在編輯器內鍵入
#include<stdio.h> int main(){int i;scanf("%d",i);//正確的應該是&i,這里使用i會導致segment faultprintf("%d\n",i);return 0; }編譯
g++ coredumpTest.cpp -g -o coredumpTest運行
./coredumpTest使用gdb調試coredump
gdb [可執行文件名] [core文件名](42) inline關鍵字說一下 和宏定義有什么區別
inline是內聯的意思,可以定義比較小的函數。因為函數頻繁調用會占用很多的棧空間,進行入棧出棧操作也耗費計算資源,所以可以用inline關鍵字修飾頻繁調用的小函數。編譯器會在編譯階段將代碼體嵌入內聯函數的調用語句塊中。
1、內聯函數在編譯時展開,而宏在預編譯時展開
2、在編譯的時候,內聯函數直接被嵌入到目標代碼中去,而宏只是一個簡單的文本替換。
3、內聯函數可以進行諸如類型安全檢查、語句是否正確等編譯功能,宏不具有這樣的功能。
4、宏不是函數,而inline是函數
5、宏在定義時要小心處理宏參數,一般用括號括起來,否則容易出現二義性。而內聯函數不會出現二義性。
6、inline可以不展開,宏一定要展開。因為inline指示對編譯器來說,只是一個建議,編譯器可以選擇忽略該建議,不對該函數進行展開。
7、宏定義在形式上類似于一個函數,但在使用它時,僅僅只是做預處理器符號表中的簡單替換,因此它不能進行參數有效性的檢測,也就不能享受C++編譯器嚴格類型檢查的好處,另外它的返回值也不能被強制轉換為可轉換的合適的類型,這樣,它的使用就存在著一系列的隱患和局限性。
(43) 模板的用法與適用場景 實現原理
用template <typename T>關鍵字進行聲明,接下來就可以進行模板函數和模板類的編寫了
編譯器會對函數模板進行兩次編譯:在聲明的地方對模板代碼本身進行編譯,這次編譯只會進行一個語法檢查,并不會生成具體的代碼。在運行時對代碼進行參數替換后再進行編譯,生成具體的函數代碼。
(44) 成員初始化列表的概念,為什么用成員初始化列表會快一些(性能優勢)?
成員初始化列表就是在類或者結構體的構造函數中,在參數列表后以冒號開頭,逗號進行分隔的一系列初始化字段。如下:
class A{ int id; string name; FaceImage face; A(int& inputID,string& inputName,FaceImage& inputFace):id(inputID),name(inputName),face(inputFace){} // 成員初始化列表 };因為使用成員初始化列表進行初始化的話,會直接使用傳入參數的拷貝構造函數進行初始化,省去了一次執行傳入參數的默認構造函數的過程,否則會調用一次傳入參數的默認構造函數。所以使用成員初始化列表效率會高一些。
另外,有三種情況是必須使用成員初始化列表進行初始化的:
- 常量成員的初始化,因為常量成員只能初始化不能賦值
- 引用類型
- 沒有默認構造函數的對象必須使用成員初始化列表的方式進行初始化
詳見C++ 初始化列表
(45) 用過C11嗎,知道C11新特性嗎?(有面試官建議熟悉C11)
-
自動類型推導auto:auto的自動類型推導用于從初始化表達式中推斷出變量的數據類型。通過auto的自動類型推導,可以大大簡化我們的編程工作
-
nullptr
:nullptr是為了解決原來C++中NULL的二義性問題而引進的一種新的類型,因為NULL實際上代表的是0,而nullptr是void*類型的 -
lambda表達式:它類似Javascript中的閉包,它可以用于創建并定義匿名的函數對象,以簡化編程工作。Lambda的語法如下:
[函數對象參數](操作符重載函數參數)mutable或exception聲明->返回值類型{函數體} -
thread類和mutex類
-
新的智能指針 unique_ptr和shared_ptr
-
更多詳見:https://blog.csdn.net/caogenwangbaoqiang/article/details/79438279
(46) C++的調用慣例(簡單一點C++函數調用的壓棧過程)
函數的調用過程:
1)從棧空間分配存儲空間
2)從實參的存儲空間復制值到形參棧空間
3)進行運算
形參在函數未調用之前都是沒有分配存儲空間的,在函數調用結束之后,形參彈出棧空間,清除形參空間。
數組作為參數的函數調用方式是地址傳遞,形參和實參都指向相同的內存空間,調用完成后,形參指針被銷毀,但是所指向的內存空間依然存在,不能也不會被銷毀。
當函數有多個返回值的時候,不能用普通的 return 的方式實現,需要通過傳回地址的形式進行,即地址/指針傳遞。
(47) C++的四種強制轉換
四種強制類型轉換操作符分別為:static_cast、dynamic_cast、const_cast、reinterpret_cast
- 1)static_cast :
用于各種隱式轉換。具體的說,就是用戶各種基本數據類型之間的轉換,比如把int換成char,float換成int等。以及派生類(子類)的指針轉換成基類(父類)指針的轉換。特性與要點:
- 它沒有運行時類型檢查,所以是有安全隱患的。
- 在派生類指針轉換到基類指針時,是沒有任何問題的,在基類指針轉換到派生類指針的時候,會有安全問題。
- static_cast不能轉換const,volatile等屬性
- 2)dynamic_cast:
用于動態類型轉換。具體的說,就是在基類指針到派生類指針,或者派生類到基類指針的轉換。
dynamic_cast能夠提供運行時類型檢查,只用于含有虛函數的類。
dynamic_cast如果不能轉換返回NULL。 - 3)const_cast:
用于去除const常量屬性,使其可以修改 ,也就是說,原本定義為const的變量在定義后就不能進行修改的,但是使用const_cast操作之后,可以通過這個指針或變量進行修改; 另外還有volatile屬性的轉換。 - 4)reinterpret_cast
幾乎什么都可以轉,用在任意的指針之間的轉換,引用之間的轉換,指針和足夠大的int型之間的轉換,整數到指針的轉換等。但是不夠安全。
(48)string的底層實現
string繼承自basic_string,其實是對char*進行了封裝,封裝的string包含了char*數組,容量,長度等等屬性。
string可以進行動態擴展,在每次擴展的時候另外申請一塊原空間大小兩倍的空間(2^n),然后將原字符串拷貝過去,并加上新增的內容。
(49)一個函數或者可執行文件的生成過程或者編譯過程是怎樣的
預處理,編譯,匯編,鏈接
- 預處理: 對預處理命令進行替換等預處理操作
- 編譯:代碼優化和生成匯編代碼
- 匯編:將匯編代碼轉化為機器語言
- 鏈接:將目標文件彼此鏈接起來
(50)set,map和vector的插入復雜度
set,map的插入復雜度就是紅黑樹的插入復雜度,是log(N)。
unordered_set,unordered_map的插入復雜度是常數,最壞是O(N).
vector的插入復雜度是O(N),最壞的情況下(從頭插入)就要對所有其他元素進行移動,或者擴容重新拷貝
(51)定義和聲明的區別
-
聲明是告訴編譯器變量的類型和名字,不會為變量分配空間
-
定義就是對這個變量和函數進行內存分配和初始化。需要分配空間,同一個變量可以被聲明多次,但是只能被定義一次
(52)typdef和define區別
#define是預處理命令,在預處理是執行簡單的替換,不做正確性的檢查
typedef是在編譯時處理的,它是在自己的作用域內給已經存在的類型一個別名
(53)被free回收的內存是立即返還給操作系統嗎?為什么
https://blog.csdn.net/YMY_mine/article/details/81180168
不是的,被free回收的內存會首先被ptmalloc使用雙鏈表保存起來,當用戶下一次申請內存的時候,會嘗試從這些內存中尋找合適的返回。這樣就避免了頻繁的系統調用,占用過多的系統資源。同時ptmalloc也會嘗試對小塊內存進行合并,避免過多的內存碎片。
(54)引用作為函數參數以及返回值的好處
對比值傳遞,引用傳參的好處:
1)在函數內部可以對此參數進行修改
2)提高函數調用和運行的效率(因為沒有了傳值和生成副本的時間和空間消耗)
如果函數的參數實質就是形參,不過這個形參的作用域只是在函數體內部,也就是說實參和形參是兩個不同的東西,要想形參代替實參,肯定有一個值的傳遞。函數調用時,值的傳遞機制是通過“形參=實參”來對形參賦值達到傳值目的,產生了一個實參的副本。即使函數內部有對參數的修改,也只是針對形參,也就是那個副本,實參不會有任何更改。函數一旦結束,形參生命也宣告終結,做出的修改一樣沒對任何變量產生影響。
用引用作為返回值最大的好處就是在內存中不產生被返回值的副本。
但是有以下的限制:
1)不能返回局部變量的引用。因為函數返回以后局部變量就會被銷毀
2)不能返回函數內部new分配的內存的引用。雖然不存在局部變量的被動銷毀問題,可對于這種情況(返回函數內部new分配內存的引用),又面臨其它尷尬局面。例如,被函數返回的引用只是作為一 個臨時變量出現,而沒有被賦予一個實際的變量,那么這個引用所指向的空間(由new分配)就無法釋放,造成memory leak
3)可以返回類成員的引用,但是最好是const。因為如果其他對象可以獲得該屬性的非常量的引用,那么對該屬性的單純賦值就會破壞業務規則的完整性。
(55)友元函數和友元類
https://www.cnblogs.com/zhuguanhao/p/6286145.html
友元提供了不同類的成員函數之間、類的成員函數和一般函數之間進行數據共享的機制。通過友元,一個不同函數或者另一個類中的成員函數可以訪問類中的私有成員和保護成員。友元的正確使用能提高程序的運行效率,但同時也破壞了類的封裝性和數據的隱藏性,導致程序可維護性變差。
1)友元函數
有元函數是定義在類外的普通函數,不屬于任何類,可以訪問其他類的私有成員。但是需要在類的定義中聲明所有可以訪問它的友元函數。
#include <iostream>using namespace std;class A { public:friend void set_show(int x, A &a); //該函數是友元函數的聲明 private:int data; };void set_show(int x, A &a) //友元函數定義,為了訪問類A中的成員 {a.data = x;cout << a.data << endl; } int main(void) {class A a;set_show(1, a);return 0; }一個函數可以是多個類的友元函數,但是每個類中都要聲明這個函數。
2)友元類
友元類的所有成員函數都是另一個類的友元函數,都可以訪問另一個類中的隱藏信息(包括私有成員和保護成員)。
但是另一個類里面也要相應的進行聲明
使用友元類時注意:
(1) 友元關系不能被繼承。
(2) 友元關系是單向的,不具有交換性。若類B是類A的友元,類A不一定是類B的友元,要看在類中是否有相應的聲明。
(3) 友元關系不具有傳遞性。若類B是類A的友元,類C是B的友元,類C不一定是類A的友元,同樣要看類中是否有相應的申明
(56) 說一下volatile關鍵字的作用
volatile的意思是“脆弱的”,表明它修飾的變量的值十分容易被改變,所以編譯器就不會對這個變量進行優化(CPU的優化是讓該變量存放到CPU寄存器而不是內存),進而提供穩定的訪問。每次讀取volatile的變量時,系統總是會從內存中讀取這個變量,并且將它的值立刻保存。
(57) STL中的sort()算法是用什么實現的,stable_sort()呢
STL中的sort是用快速排序和插入排序結合的方式實現的,stable_sort()是歸并排序。
(58)vector會迭代器失效嗎?什么情況下會迭代器失效?
https://www.cnblogs.com/qingjiaowoxiaoxioashou/p/5874572.html
- 會
- 當vector在插入的時候,如果原來的空間不夠,會將申請新的內存并將原來的元素移動到新的內存,此時指向原內存地址的迭代器就失效了,first和end迭代器都失效
- 當vector在插入的時候,end迭代器肯定會失效
- 當vector在刪除的時候,被刪除元素以及它后面的所有元素迭代器都失效。
(58)為什么C++沒有實現垃圾回收?
- 首先,實現一個垃圾回收器會帶來額外的空間和時間開銷。你需要開辟一定的空間保存指針的引用計數和對他們進行標記mark。然后需要單獨開辟一個線程在空閑的時候進行free操作。
- 垃圾回收會使得C++不適合進行很多底層的操作。
(59)移動構造函數
- C++11之后,新增加了兩個函數:移動構造函數(Move Constructor)和移動賦值運算符(Move Assignment operator)。
- 有時候我們會遇到這樣一種情況,我們用對象a初始化對象b,后對象a我們就不在使用了,但是對象a的空間還在呀(在析構之前),既然拷貝構造函數,實際上就是把a對象的內容復制一份到b中,那么為什么我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構造的成本。這就是移動構造函數設計的初衷。
2. 計網相關
(1) 建立TCP服務器的各個系統調用
建立TCP服務器連接的過程中主要通過以下系統調用序列來獲取某些函數,這些系統調用主要包括:socket(),bind(),listen(),accept(),send()和recv()。
詳見:建立TCP 服務器的系統調用
(2) 繼上一題,說明socket網絡編程有哪些系統調用?其中close是一次就能直接關閉的嗎,半關閉狀態是怎么產生的?
socket() 創建套接字 bind() 綁定本機端口 connect() 建立連接 (TCP三次握手在調用這個函數時進行) listen() 監聽端口 accept() 接受連接 recv(), read(), recvfrom() 數據接收 send(), write(), sendto() 數據發送 close(), shutdown() 關閉套接字使用close()時,只有當套接字的引用計數為0的時候才會終止連接,而用shutdown()就可以直接關閉連接
詳見:網絡編程Socket之TCP之close/shutdown詳解
TCP連接與斷開詳解: https://www.cnblogs.com/felixzh/p/8359066.html
(3) 對路由協議的了解與介紹。內部網關協議IGP包括RIP,OSPF,和外部網關協議EGP和BGP.
-
RIP“路由信息協議(Route Information Protocol)”的簡寫,主要傳遞路由信息,通過每隔30秒廣播一次路由表,維護相鄰路由器的位置關系,同時根據收到的路由表信息使用動態規劃的方式計算自己的路由表信息。RIP是一個距離矢量路由協議,最大跳數為16跳,16跳以及超過16跳的網絡則認為目標網絡不可達。
-
OSPF:詳見:https://zhuanlan.zhihu.com/p/41341540
(4) UDP如何實現可靠傳輸
因為UDP是無連接的協議,所以在傳輸層上無法保證可靠傳輸,要想實現可靠傳輸,只能從應用層實現。需要實現seq/ack機制,重傳機制和窗口確認機制。
就要接收方收到UDP之后回復個確認包,發送方有個機制,收不到確認包就要重新發送,每個包有遞增的序號,接收方發現中間丟了包就要發重傳請求,當網絡太差時候頻繁丟包,防止越丟包越重傳的惡性循環,要有個發送窗口的限制,發送窗口的大小根據網絡傳輸情況調整,調整算法要有一定自適應性。
作者:姚冬
鏈接:https://www.zhihu.com/question/283995548/answer/661809748
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
(5) TCP和UDP的區別
- TCP是面向連接的協議,提供的是可靠傳輸,在收發數據前需要通過三次握手建立連接,使用ACK對收發的數據進行正確性檢驗。而UDP是無連接的協議,不管對方有沒有收到或者收到的數據是否正確。
- TCP提供流量控制和擁塞控制,而UDP沒有。
- TCP對系統資源的要求高于UDP,所以速度也比UDP慢。
- TCP數據包是沒有邊界的,會出現粘包的問題,UDP包是獨立的,不會出現粘包問題。
- 所以在應用方面,如果強調數據的完整性和正確性用TCP,當要求性能和速度的時候,使用UDP更加合適。
注:單憑TCP是不能保證完整性的,要是有黑客偽造TCP包,是無法識別的。
(6) TCP和UDP相關的協議與端口號
TCP族的協議有HTTP,HTTPS,SMTP,TelNet,FTP等,UDP族的協議有DNS,DHCP等等。
詳見:https://blog.csdn.net/qq_22080999/article/details/81105051
(7) TCP(UDP,IP)等首部的認識(http請求報文構成)
TCP的頭部大致包括:源端口,目的端口,序號,確認號,偏移位,標志位,校驗和等等
UDP的頭部則包括:源端口,目的端口,長度,校驗和。
IP數據包的頭部包括:源IP地址,目的IP地址,協議,校驗和,總長度等等
詳見:https://blog.csdn.net/zhangliangzi/article/details/52554439
(8) 網頁解析的過程與實現方法
這里僅展示瀏覽器解析服務器響應的過程,URL解析和交互的完整過程在(9)
- 首先是html文檔解析,瀏覽器會將html文檔生成解析樹,也就是DOM樹,它由dom元素以及屬性節點組成。
- 然后瀏覽器加載過程中如果遇到了外部css文件或者圖片資源,還會另外發送請求來獲取css文件和資源,這個請求通常是異步的,不會影響html文檔的加載。
- 不過如果瀏覽器在加載時遇到了js文件,則會掛起渲染的線程,等待js文件加載解析完畢才恢復html的渲染線程。
- 然后是css解析,將css文件解析為樣式表對象來渲染DOM樹。
(9) 在瀏覽器中輸入URL后執行的全部過程(如www.baidu.com)
(10) 網絡層分片的原因與具體實現
因為在鏈路層中幀的大小通常都有限制,比如在以太網中幀的最大大小(MTU)就是1500字節。如果IP數據包加上頭部后大小超過1500字節,就需要分片。
IP分片和完整IP報文差不多擁有相同的IP頭,16位ID域對于每個分片都是一致的,這樣才能在重新組裝的時候識別出來自同一個IP報文的分片。在IP頭里面,16位識別號唯一記錄了一個IP包的ID,具有同一個ID的IP分片將會重新組裝;而13位片偏移則記錄了某IP片相對整個包的位置;而這兩個表中間的3位標志則標志著該分片后面是否還有新的分片。這三個標志就組成了IP分片的所有信息(將在后面介紹),接受方就可以利用這些信息對IP數據進行重新組織。
詳見:https://blog.csdn.net/gettogetto/article/details/72851734
(11) TCP的三次握手與四次揮手的詳細介紹(TCP連接建立與斷開是熱門問題)
- 三次握手
第一次握手:首先client給server發送連接請求報文,在這個報文中,包含了SYN=1,client_seq=任意值i,發送之后處于SYN-SENT狀態,這是第一次握手
第二次握手:server端接收到了這個請求,并分配資源,同時給client返回一個ACK報文,這個報文中呢包含了這些字段,標志位SYN和ACK都為1,而小ack為i+1,此時位于SYN-RCVD狀態,這是第二次握手
第三次握手:client收到server發來的ACK信息后呢,他會看到server發過來的小ack是i+1,這時他知道了server收到了消息,也給server回一個ACK報文,報文中同樣包含了ACK=1這樣的消息,同時呢,還包括了client_ack=k+1這樣的字段,這樣呢三次握手之后,連接就建立了,client進入established(已建立連接)狀態
- 四次揮手斷開連接:
TCP斷開連接通常是由一方主動,一方被動的,這里我們假設client主動,server被動
第一次揮手:當client沒有數據要發送給server了,他會給server發送一個FIN報文,告訴server:“我已經沒有數據要發給你了,但是你要是還想給我發數據的話,你就接著發,但是你得告訴我你收到我的關閉信息了”,這是第一次揮手,揮手之后client進入FIN_WAIT_1的第一階段
第二次揮手:當server收到client發來的FIN報文后,告訴client:“我收到你的FIN消息了,但是你等我發完的”此時給client返回一個ACK信息,并且呢ack=seq+1,這是第二次揮手,揮手之后呢server進入CLOSE_WAIT階段,而client收到之后處于FIN_WAIT_2第二階段
第三次揮手:當server發完所有數據時,他會給client發送一個FIN報文,告訴client說“我傳完數據了,現在要關閉連接了”,然后呢server變成LAST_ACK狀態,等著client最后的ACK信息,這是第三次揮手
第四次揮手:當client收到這個FIN報文時,他會對這個消息進行確認,即給server發ACK信息,但是它不相信網絡,怕server收不到信息,它會進入TIME_WAIT狀態,萬一server沒收到ACK消息它可以可以重傳,而當server收到這個ACK信息后,就正式關閉了tcp連接,處于CLOSED狀態,而client等待了2MSL這樣長時間后還沒等到消息,它知道server已經關閉連接了,于是乎他自己也斷開了,這是第四次揮手,這樣tcp連接就斷開了
(12) TCP握手以及每一次握手客戶端和服務器端處于哪個狀態
見上
(13) 為什么使用三次握手,兩次握手可不可以?
如果使用兩次握手的話,三次握手中的最后一次缺失,服務器不能確認客戶端的接收能力。
舉兩個例子,第一種是黑客會偽造大量SYN請求發送給服務器,服務器立即確認并建立連接,分配資源,但是這一系列連接并不是真實存在的,這大大浪費了服務器的資源并且阻塞了正常用戶的連接,這種也叫SYN洪泛攻擊。第二種是服務器返回給客戶端的ACK數據包可能會在傳輸的過程中丟失,而客戶端沒有收到該ACK數據包而拒絕接收服務器接下來發送的數據,于是服務器一直在發送,客戶端一直在拒絕,形成死鎖。
(14) TIME_WAIT的意義(為什么要等于2MSL)
TIME_WAIT是指四次揮手中客戶端接收了服務端的FIN報文并發送ACK報文給服務器后,仍然需要等待2MSL時間的過程。雖然按道理,四個報文都發送完畢,我們可以直接進入CLOSE狀態了,但是我們必須假象網絡是不可靠的,有可以最后一個ACK丟失。如果客戶端發送的ACK發生丟失,服務器會再次發送FIN報文給客戶端,所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。
(15) 超時重傳機制(不太高頻)
(16) TCP怎么保證可靠性?
(校序重流擁)
-
校驗和
發送的數據包的二進制相加然后取反,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段。 -
確認應答+序列號
TCP給發送的每一個包進行編號,接收方對數據包進行排序,把有序數據傳送給應用層。 -
超時重傳
當TCP發出一個段后,它啟動一個定時器,等待目的端確認收到這個報文段。如果不能及時收到一個確認,將重發這個報文段。 -
流量控制
TCP連接的每一方都有固定大小的緩沖空間,TCP的接收端只允許發送端發送接收端緩沖區能接納的數據。當接收方來不及處理發送方的數據,能提示發送方降低發送的速率,防止包丟失。TCP使用的流量控制協議是可變大小的滑動窗口協議。
接收方有即時窗口(滑動窗口),隨ACK報文發送 -
擁塞控制
當網絡擁塞時,減少數據的發送。
發送方有擁塞窗口,發送數據前比對接收方發過來的即使窗口,取小
慢啟動、擁塞避免、快速重傳、快速恢復
(17) 流量控制的介紹,采用滑動窗口會有什么問題(死鎖可能,糊涂窗口綜合征)?
所謂流量控制就是讓發送方發送速率不要過快,讓接收方來得及接收。利用TCP報文段中的窗口大小字段來控制發送方的發送窗口不大于接收方發回的窗口大小就可以實施流量控制。
考慮一種特殊的情況,就是接收方若沒有緩存足夠使用,就會發送零窗口大小的報文,此時發送放將發送窗口設置為0,停止發送數據。之后接收方有足夠的緩存,發送了非零窗口大小的報文,但是這個報文在中途丟失的,那么發送方的發送窗口就一直為零導致死鎖。
解決這個問題,TCP為每一個連接設置一個持續計時器(persistence timer)。只要TCP的一方收到對方的零窗口通知,就啟動該計時器,周期性的發送一個零窗口探測報文段。對方就在確認這個報文的時候給出現在的窗口大小(注意:TCP規定,即使設置為零窗口,也必須接收以下幾種報文段:零窗口探測報文段、確認報文段和攜帶緊急數據的報文段)。
(18) tcp滑動窗口協議
詳見 TCP-IP詳解:滑動窗口SlidingWindow和TCP滑動窗口
TCP的滑動窗口用來控制接收方和發送方的發送速率,避免擁塞的發生。滑動窗口其實就是接收端的緩沖區大小,用來告訴發送方對它發送的數據有多大的緩沖空間。在接收方的滑動窗口已知的情況下,當接收方確認了連續的數據序列之后,發送方的滑動窗口向后滑動,發送下一個數據序列。
接收方會在每個ACK數據包中附帶自己當前的接受窗口(滑動窗口)的大小,方便發送方進行控制。
(19) 擁塞控制和流量控制的區別
擁塞控制是防止過多的數據注入到網絡中,導致網絡發生擁塞;而流量控制是防止發送方一下子發送過多的數據到接收方,導致接收方緩存放不下。兩種算法都是對發送方的行為進行控制的。
(20) TCP擁塞控制,算法名字?(極其重要)
防止過多的數據注入到網絡中,這樣可以使網絡中的路由器或鏈路不致過載,擁塞控制自然也是控制發送者的流量,擁塞控制有四種算法,慢啟動、擁塞避免,快速重傳和快速恢復
發送方維持一個擁塞窗口 cwnd ( congestion window )的狀態變量。擁塞窗口的大小取決于網絡的擁塞程度,并且動態地在變化。發送方讓自己的發送窗口等于擁塞窗口和接受窗口的較小值。
(1)慢啟動。慢啟動算法的思路是當主機開始發送數據時,先以比較小的擁塞窗口進行發送,然后每次翻倍,也就是說,由小到大逐漸增加擁塞窗口的大小,而這個大小是指數增長的,即1、2、4、8、16
*為了防止擁塞窗口cwnd增長過大引起網絡擁塞,還要另外設置一個慢啟動閾值ssthresh狀態變量,當擁塞窗口的大小超過慢啟動閾值的時候( cwnd > ssthresh 時),停止使用慢開始算法而改用擁塞避免算法
(2)擁塞避免。擁塞避免算法的思路是讓擁塞窗口cwnd緩慢地增大,即每經過一個往返時間RTT就把發送方的擁塞窗口cwnd加1,而不是加倍。
(3)快速重傳。當發送端連續收到三個重復的ack時,表示該數據段已經丟失,需要重發。此時慢啟動閾值ssth變為原來一半,擁塞窗口cwnd變為ssth+3,然后+1+1的發(每一輪rtt+1)
(4)快速恢復。當超過設定的時間沒有收到某個報文段的ack時,表示網絡擁塞,慢啟動閾值ssth變為原來一半,擁塞窗口cwnd=1,進入慢啟動階段
(21) http協議與TCP的區別與聯系
聯系:Http協議是建立在TCP協議基礎之上的,當瀏覽器需要從服務器獲取網頁數據的時候,會發出一次Http請求。Http會通過TCP建立起一個到服務器的連接通道,當本次請求需要的數據傳輸完畢后,Http會立即將TCP連接斷開,這個過程是很短的。
區別:HTTP和TCP位于不同的網絡分層。TCP是傳輸層的協議,定義的是數據傳輸和連接的規范,而HTTP是應用層的,定義的是數據的內容的規范。
建立一個TCP請求需要進行三次握手,而由于http是建立在tcp連接之上的,建立一個http請求通常包含請求和響應兩個步驟。
(22) http/1.0和http/1.1 和 http/2.0的區別
HTTP 協議老的標準是 HTTP/1.0 ,目前最通用的標準是 HTTP/1.1 。
-
緩存處理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires來做為緩存判斷的標準,HTTP1.1則引入了更多的緩存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供選擇的緩存頭來控制緩存策略。
-
帶寬優化及網絡連接的使用,HTTP1.0中,存在一些浪費帶寬的現象,例如客戶端只是需要某個對象的一部分,而服務器卻將整個對象送過來了,并且不支持斷點續傳功能,HTTP1.1則在請求頭引入了range頭域,它允許只請求資源的某個部分,即返回碼是206(Partial Content),這樣就方便了開發者自由的選擇以便于充分利用帶寬和連接。
-
錯誤通知的管理,在HTTP1.1中新增了24個錯誤狀態響應碼,如409(Conflict)表示請求的資源與資源的當前狀態發生沖突;410(Gone)表示服務器上的某個資源被永久性的刪除。
-
Host頭處理,在HTTP1.0中認為每臺服務器都綁定一個唯一的IP地址,因此,請求消息中的URL并沒有傳遞主機名(hostname)。但隨著虛擬主機技術的發展,在一臺物理服務器上可以存在多個虛擬主機(Multi-homed Web Servers),并且它們共享一個IP地址。HTTP1.1的請求消息和響應消息都應支持Host頭域,且請求消息中如果沒有Host頭域會報告一個錯誤(400 Bad Request)。
-
長連接,HTTP 1.1支持長連接(PersistentConnection)和請求的流水線(Pipelining)處理,在一個TCP連接上可以傳送多個HTTP請求和響應,減少了建立和關閉連接的消耗和延遲,在HTTP1.1中默認開啟Connection: keep-alive,一定程度上彌補了HTTP1.0每次請求都要創建連接的缺點。
HTTP2.0和HTTP1.X相比的新特性
- 新的二進制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本協議的格式解析存在天然缺陷,文本的表現形式有多樣性,要做到健壯性考慮的場景必然很多,二進制則不同,只認0和1的組合。基于這種考慮HTTP2.0的協議解析決定采用二進制格式,實現方便且健壯。
- 多路復用(MultiPlexing),即連接共享,即每一個request都是是用作連接共享機制的。一個request對應一個id,這樣一個連接上可以有多個request,每個連接的request可以隨機的混雜在一起,接收方可以根據request的 id將request再歸屬到各自不同的服務端請求里面。
- header壓縮,如上文中所言,對前面提到過HTTP1.x的header帶有大量信息,而且每次都要重復發送,HTTP2.0使用encoder來減少需要傳輸的header大小,通訊雙方各自cache一份header fields表,既避免了重復header的傳輸,又減小了需要傳輸的大小。
- 服務端推送(server push),同SPDY一樣,HTTP2.0也具有server push功能。
(23) http的請求方法有哪些?get和post的區別。
HTTP的請求方法包括GET,POST,PUT,DELETE四種基本方法。(四種方法中只有POST不是操作冪等性的)
get和post的區別:
(24) http的狀態碼 403 201等等是什么意思
詳見 HTTP狀態碼的含義
常見的狀態碼有:
- 200 - 請求成功
- 301 - 資源(網頁等)被永久轉移到其它URL
- 400 - 請求無效
- 403 - 禁止訪問
- 404 - 請求的資源(網頁等)不存在
- 500 - 內部服務器錯誤
- 502 - Bad Gateway,錯誤網關,無效網關;
(25) http和https的區別,由http升級為https需要做哪些操作
http 是超文本傳輸協議,信息是明文傳輸, https 則是具有安全性的 ssl 加密傳輸協議
http 和 https 使用的是完全不同的連接方式,用的端口也不一樣,前者是 80 ,后者是 443
http 的連接很簡單,是無狀態的; HTTPS 協議是由 SSL+HTTP 協議構建的可進行加密傳輸、身份認證的網絡協議,比http 協議安全。
https 協議需要到 ca 申請證書,一般免費證書較少,因而需要一定費用
https://www.cnblogs.com/wqhwe/p/5407468.html
(26) https的具體實現,怎么確保安全性
SSL是傳輸層的協議
https包括非對稱加密和對稱加密兩個階段,在客戶端與服務器建立連接的時候使用非對稱加密,連接建立以后使用的是對稱加密。
服務器第一次傳給客戶端的公鑰其實是CA對網站信息進行加密的數字證書
客戶端的對稱加密密鑰其實是三個隨機數的哈希(1. 客戶端第一次給服務端發送請求時附帶的隨機數 2. 服務器返回時的隨機數 3. 客戶端收到返回時的隨機數)
(27) TCP三次握手時的第一次的seq序號是怎樣產生的
第一次的序號是隨機序號,但也不是完全隨機,它是使用一個ISN算法得到的。
seq = C + H (源IP地址,目的IP地址,源端口,目的端口)。其中,C是一個計時器,每隔一段時間值就會變大,H是消息摘要算法,輸入是一個四元組(源IP地址,目的IP地址,源端口,目的端口)。
(28) 一個機器能夠使用的端口號上限是多少,為什么?可以改變嗎?那如果想要用的端口超過這個限制怎么辦?
65536.因為TCP的報文頭部中源端口號和目的端口號的長度是16位,也就是可以表示2^16=65536個不同端口號,因此TCP可供識別的端口號最多只有65536個。但是由于0到1023是知名服務端口,所以實際上還要少1024個端口號。
而對于服務器來說,可以開的端口號與65536無關,其實是受限于Linux可以打開的文件數量,并且可以通過MaxUserPort來進行配置。
(29) 對稱密碼和非對稱密碼體系
https://blog.csdn.net/qq_29689487/article/details/81634057
- 對稱加密:加密和解密使用的密鑰是同一個
- 優點:計算量小,算法速度快,加密效率高 缺點:密鑰容易泄漏。不同的會話需要不同的密鑰,管理起來很費勁
- 常用算法:DES,3DES,IDEA,CR4,CR5,CR6,AES
- 非對稱加密:需要公鑰和私鑰,公鑰用來加密,私鑰用來解密
- 優點:安全,不怕泄漏 缺點:速度慢
- 常用算法:RSA,ECC,DSA
(30) 數字證書的了解(高頻)
權威CA使用私鑰將網站A的信息和消息摘要(簽名S)進行加密打包形成數字證書。公鑰給客戶端。
網站A將自己的信息和數字證書發給客戶端,客戶端用CA的公鑰對數字證書進行解密,得到簽名S,與手動將網站的信息進行消息摘要得到的結果S*進行對比,如果簽名一致就證明網站A可以信任。
(31) 服務器出現大量close_wait的連接的原因以及解決方法
close_wait狀態是在TCP四次揮手的時候收到FIN但是沒有發送自己的FIN時出現的,服務器出現大量close_wait狀態的原因有兩種:
- 服務器內部業務處理占用了過多時間,都沒能處理完業務;或者還有數據需要發送;或者服務器的業務邏輯有問題,沒有執行close()方法
- 服務器的父進程派生出子進程,子進程繼承了socket,收到FIN的時候子進程處理但父進程沒有處理該信號,導致socket的引用不為0無法回收
處理方法:
- 停止應用程序
- 修改程序里的bug
(32) 消息摘要算法列舉一下,介紹MD5算法,為什么MD5是不可逆的,有什么辦法可以加強消息摘要算法的安全性讓它不那么容易被破解呢?(百度安全一面)
-
消息摘要算法有MD家族(MD2,MD4,MD5),SHA家族(SHA-1,SHA-256)和CRC家族(CRC8,CRC16,CRC32)等等
-
MD5算法介紹:
MD5以512位分組來處理輸入的信息,且每一分組又被劃分為若干個小分組(16個32位子分組),經過一些列的處理后,算法輸出由四個散列值(32位分組組成的128位散列值。)
詳見:https://blog.csdn.net/weixin_39640298/article/details/84555814
-
為什么不可逆:因為MD5在進行消息摘要的過程中,數據與原始數據相比發生了丟失,所以不能由結果進行恢復。
-
加強安全性:加鹽(加隨機數)
(33) 單條記錄高并發訪問的優化
服務器端:
- 使用緩存,如redis等
- 使用分布式架構進行處理
- 將靜態頁面和靜態資源存儲在靜態資源服務器,需要處理的數據使用服務器進行計算后返回
- 將靜態資源盡可能在客戶端進行緩存
- 采用ngnix進行負載均衡 (nginx讀作恩靜埃克斯 = Engine X)
數據庫端:
- 數據庫采用主從賦值,讀寫分離措施
- 建立適當的索引
- 分庫分表
(34) 介紹一下ping的過程,分別用到了哪些協議
詳見:Ping原理與ICMP協議
ping是使用ICMP協議來進行工作的。 ICMP:網絡控制報文協議
- 首先,ping命令會構建一個ICMP請求數據包,然后由ICMP協議將這個數據包連同目的IP地址源IP地址一起交給IP協議。
- 然后IP協議就會構建一個IP數據報,并且在映射表中查找目的IP對應的mac地址,將其交給數據鏈路層。
- 然后數據鏈路層就會構建一個數據幀,附上源mac地址和目的mac地址發送出去。
目的主機接收到數據幀后,就會檢查包上的mac地址與本機mac是否相符,如果相符,就接收并把其中的信息提取出來交給IP協議,IP協議就會將其中的信息提取出來交給ICMP協議。然后構建一個ICMP應答包,用相同的過程發送回去。
(35) TCP/IP的粘包與避免介紹一下
因為TCP為了減少額外開銷,采取的是流式傳輸,所以接收端在一次接收的時候有可能一次接收多個包。而TCP粘包就是發送方的若干個數據包到達接收方的時候粘成了一個包。多個包首尾相接,無法區分。
導致TCP粘包的原因有三方面:
- 發送端等待緩沖區滿才進行發送,造成粘包
- 接收方來不及接收緩沖區內的數據,造成粘包
- 由于TCP協議在發送較小的數據包的時候,會將幾個包合成一個包后發送
避免粘包的措施:
- 通過編程,強制使TCP發生數據傳送,不必等到緩沖區滿
- 優化接收方接收數據的過程,使其來得及接收數據包,包括提高接收進程優先級等
- 設置固定長度的報文或者設置報文頭部指示報文的長度。
(36) 說一下TCP的封包和拆包
因為TCP是無邊界的流傳輸,所以需要對TCP進行封包和拆包,確保發送和接收的數據不粘連。
- 封包:封包就是在發送數據報的時候為每個TCP數據包加上一個包頭,將數據報分為包頭和包體兩個部分。包頭是一個固定長度的結構體,里面包含該數據包的總長度。
- 拆包:接收方在接收到報文后提取包頭中的長度信息進行截取。
(37) 一個ip配置多個域名,靠什么識別?
- 靠host主機名區分
- 靠端口號區分
(38) 服務器攻擊(DDos攻擊)
(39)DNS的工作過程和原理
DNS解析有兩種方式:遞歸查詢和迭代查詢
- 遞歸查詢 用戶先向本地域名服務器查詢,如果本地域名服務器的緩存沒有IP地址映射記錄,就向根域名服務器查詢,根域名服務器就會向頂級域名服務器查詢,頂級域名服務器向權限域名服務器查詢,查到結果后依次返回。
- 迭代查詢 用戶向本地域名服務器查詢,如果沒有緩存,本地域名服務器會向根域名服務器查詢,根域名服務器返回頂級域名服務器的地址,本地域名服務器再向頂級域名服務器查詢,得到權限域名服務器的地址,本地域名服務器再向權限域名服務器查詢得到結果
(41)OSA七層協議和五層協議,分別有哪些
OSI七層協議模型主要是:應用層(Application)、表示層(Presentation)、會話層(Session)、傳輸層(Transport)、網絡層(Network)、數據鏈路層(Data Link)、物理層(Physical)。
五層體系結構包括:應用層、傳輸層、網絡層、數據鏈路層和物理層。
(42)IP尋址和MAC尋址有什么不同,怎么實現的
通過MAC地址尋找主機是MAC地址尋址,通過IP地址尋找主機叫IP地址尋址。它們適用于不同的協議層,IP尋址是網絡層,Mac尋址是數據鏈路層。
http://c.biancheng.net/view/6388.html
https://blog.csdn.net/wxy_nick/article/details/9190693
IP尋址的過程(ARP協議):主機A想通過IP地址尋找到目標主機,首先分析IP地址確定目標主機與自己是否為同一網段。如果是則查看ARP緩存,或者使用ARP協議發送廣播。如果不是,則尋找網關發送ARP數據包
(43)ARP協議
工作過程
- 第一步:首先,每個主機都會有自己的ARP緩存區中建立一個ARP列表,以表示IP地址和MAC地址之間的對應關系
- 第二步:當源主機要發送數據時,首先檢測ARP列表中是否對應IP地址的目的主機的MAC地址
如果有,則直接發送數據。如果沒有,就向本網段的所有主機發送ARP數據包,內容:我是IP地址,mac地址,誰是IP地址,mac? - 第三步:當本網絡的所有主機收到該ARP數據包時,首先檢查數據包中的IP地址是否是自己的IP地址,如果不是,則忽略該數據包。如果是,則首先從數據包中取出源主機的IP和mac地址寫入到ARP列表中,如果以存在,則覆蓋。然后將自己的mac地址寫入arp響應包中,告訴源主機自己是它想要找的mac地址
- 第四步:源主機收到ARP響應包后,將目的主機的IP和mac地址寫入arp列表,并利用此信息發送數據
如果源主機一直沒有收到arp響應數據包,表示arp查詢失敗。
為什么要使用ARP協議
OSI模型把網絡工作分為七層,彼此不直接打交道,只通過接口(layer interface)。IP地址工作在第三層,MAC地址工作在第二層。當協議在發送數據包時,需要先封裝第三層IP地址,第二層MAC地址的報頭,但協議只知道目的節點的IP地址,不知道目的節點的MAC地址,又不能跨第二、三層,所以得用ARP協議服務,來幫助獲取到目的節點的MAC地址。
ARP協議是第幾層協議
工作在二層(鏈路層),是三層協議。
ARP在生成環境產生的問題及解決辦法:
ARP病毒,ARP欺騙。
高可用服務器對之間切換時要考慮ARP緩存的問題。
路由器等設備無縫遷移時要考慮ARP緩存的問題,例如:更換辦公室的路由器。
3. 數據庫
(1) 關系型和非關系型數據庫的區別(低頻)
- 關系型數據庫的優點
- 容易理解。因為它采用了關系模型來組織數據。
- 可以保持數據的一致性。
- 數據更新的開銷比較小。
- 支持復雜查詢(帶where子句的查詢)
- 非關系型數據庫的優點
- 不需要經過sql層的解析,讀寫效率高。
- 基于鍵值對,數據的擴展性很好。
- 可以支持多種類型數據的存儲,如圖片,文檔等等。
(2) 什么是非關系型數據庫(低頻)
非關系型數據庫也叫nosql,采用鍵值對的形式進行存儲。它的讀寫性能很高,易于擴展。例如Redis,Mongodb,hbase等等。
適合使用非關系型數據庫的場景:
- 日志系統
- 地理位置存儲
- 數據量巨大
- 高可用
(3) 說一下 MySQL 執行一條查詢語句的內部執行過程?
- 連接器:客戶端先通過連接器連接到 MySQL 服務器。
- 緩存:連接器權限驗證通過之后,先查詢是否有查詢緩存,如果有緩存(之前執行過此語句)則直接返回緩存數據,如果沒有緩存則進入分析器。
- 分析器:分析器會對查詢語句進行語法分析和詞法分析,判斷 SQL 語法是否正確,如果查詢語法錯誤會直接返回給客戶端錯誤信息,如果語法正確則進入優化器。
- 優化器:優化器是對查詢語句進行優化處理,例如一個表里面有多個索引,優化器會判別哪個索引性能更好。
- 執行器:優化器執行完就進入執行器,執行器就開始執行語句進行查詢比對了,直到查詢到滿足條件的所有數據,然后進行返回。
(4) 數據庫的索引類型
數據庫的索引類型分為邏輯分類和物理分類
邏輯分類:
- 主鍵索引 當關系表中定義主鍵時會自動創建主鍵索引。每張表中的主鍵索引只能有一個,要求主鍵中的每個值都唯一,即不可重復,也不能有空值。
- 唯一索引 數據列不能有重復,可以有空值。一張表可以有多個唯一索引,但是每個唯一索引只能有一列。如身份證,卡號等。
- 普通索引 一張表可以有多個普通索引,可以重復可以為空值
- 全文索引 可以加快模糊查詢,不常用
物理分類:
- 聚集索引(聚簇索引) 數據在物理存儲中的順序跟索引中數據的邏輯順序相同,比如以ID建立聚集索引,數據庫中id從小到大排列,那么物理存儲中該數據的內存地址值也按照從小到大存儲。一般是表中的主鍵索引,如果沒有主鍵索引就會以第一個非空的唯一索引作為聚集索引。一張表只能有一個聚集索引。
- 非聚集索引 數據在物理存儲中的順序跟索引中數據的邏輯順序不同。非聚集索引因為無法定位數據所在的行,所以需要掃描兩遍索引樹。第一遍掃描非聚集索引的索引樹,確定該數據的主鍵ID,然后到主鍵索引(聚集索引)中尋找相應的數據。
(5) 說一下事務是怎么實現的
https://blog.csdn.net/u013256816/article/details/103966510
https://www.cnblogs.com/takumicx/p/9998844.html
事務就是一組邏輯操作的集合。實現事務就是要保證可靠性和并發隔離,或者說,能夠滿足ACID特性的機制。而這些主要是靠日志恢復和并發控制實現的。
- 日志恢復:數據庫里有兩個日志,一個是redo log,一個是undo log。redo log記錄的是已經成功提交的事務操作信息,用來恢復數據,保證事務的持久性。undo log記錄的是事務修改之前的數據信息,用來回滾數據,保證事務的原子性。
- 并發控制:并發控制主要靠讀寫鎖和MVCC(多版本并發控制)來實現。讀寫鎖包括共享鎖和排他鎖,保證事務的隔離性。MVCC通過為數據添加時間戳來實現。
(6) MySQL怎么建立索引,怎么建立主鍵索引,怎么刪除索引?
MySQL建立索引有兩種方式:用alter table或者create index。
alter table table_name add primary key(column_list) #添加一個主鍵索引 alter table table_name add index (column_list) #添加一個普通索引 alter table table_name add unique (column_list) #添加一個唯一索引 create index index_name on table_name (column_list) #創建一個普通索引 create unique index_name on table_name (column_list) #創建一個唯一索引Mysql刪除索引同樣也有兩種方式:alter table 和 drop index
alter table table_name drop index index_name #刪除一個普通索引 alter table table_name drop primary key #刪除一個主鍵索引 drop index index_name on table table_name(7) 索引的優缺點,什么時候使用索引,什么時候不能使用索引(重點)
https://www.cnblogs.com/wezheng/p/8399305.html
- 經常搜索的列上建索引
- 作為主鍵的列上要建索引
- 經常需要連接(where子句)的列上
- 經常需要排序的列
- 經常需要范圍查找的列
哪些列不適合建索引?
- 很少查詢的列
- 更新很頻繁的列
- 數據值的取值比較少的列(比如性別)
(8) 索引的底層實現(重點)
數據庫的索引是使用B+樹來實現的。
(為什么要用B+樹,為什么不用紅黑樹和B樹)
B+樹是一種特殊的平衡多路樹,是B樹的優化改進版本,它把所有的數據都存放在葉節點上,中間節點保存的是索引。這樣一來相對于B樹來說,減少了數據對中間節點的空間占用,使得中間節點可以存放更多的指針,使得樹變得更矮,深度更小,從而減少查詢的磁盤IO次數,提高查詢效率。另一個是由于葉節點之間有指針連接,所以可以進行范圍查詢,方便區間訪問。
而紅黑樹是二叉的,它的深度相對B+樹來說更大,更大的深度意味著查找次數更多,更頻繁的磁盤IO,所以紅黑樹更適合在內存中進行查找。
(9) B樹和B+樹的區別(重點)
這都是由于B+樹和B具有不同的存儲結構所造成的區別,以一個m階樹為例。
B+樹優點:由于B+樹的數據都存儲在葉子結點中,分支結點均為索引,方便掃庫,只需要掃一遍葉子結點即可,但是B樹因為其分支結點同樣存儲著數據,我們要找到具體的數據,需要進行一次中序遍歷按序來掃,所以B+樹更加適合在區間查詢的情況,所以通常B+樹用于數據庫索引,而B樹則常用于文件索引。
(10) 索引最左前綴/最左匹配
假如我們對a b c三個字段建立了聯合索引,在聯合索引中,從最左邊的字段開始,任何連續的索引都能匹配上,當遇到范圍查詢的時候停止。比如對于聯合索引index(a,b,c),能匹配a,ab,abc三組索引。并且對查詢時字段的順序沒有限制,也就是a,b,c; b,a,c; c,a,b; c,b,a都可以匹配。
(11) Mysql的優化(高頻,索引優化,性能優化)
高頻訪問:
- 分表分庫:將數據庫表進行水平拆分,減少表的長度
- 增加緩存: 在web和DB之間加上一層緩存層
- 增加數據庫的索引:在合適的字段加上索引,解決高頻訪問的問題
并發優化:
- 主從讀寫分離:只在主服務器上寫,從服務器上讀
- 負載均衡集群:通過集群或者分布式的方式解決并發壓力
(12) MYSQL數據庫引擎介紹,innodb和myisam的特點與區別
- InnoDB : InnoDB是mysql的默認引擎,支持事務和外鍵,支持容災恢復。適合更新頻繁和多并發的表 行級鎖
- MyISAM : 插入和查詢速度比較高,支持大文件,但是不支持事務,適合在web和數據倉庫場景下使用 表級鎖
- MEMORY : memory將表中的數據保存在內存里,適合數據比較小而且頻繁訪問的場景
- CSV
- blackhole
(13) 數據庫中事務的ACID(四大特性都要能夠舉例說明,理解透徹,比如原子性和一致性的關聯,隔離性不好會出現的問題)
數據庫事務是指邏輯上對數據的一種操作,這個事務要么全部成功,要么全部失敗。
A: atom 原子性
數據庫事務的原子性是指:事務是一個不可分割的工作單位,這組操作要么全部發生,要么全部不發生。
C: consistency 一致性
數據庫事務的一致性是指:在事務開始以前,數據庫中的數據有一個一致的狀態。在事務完成后,數據庫中的事務也應該保持這種一致性。事務應該將數據從一個一致性狀態轉移到另一個一致性狀態。
比如在銀行轉賬操作后兩個賬戶的總額應當不變。
I: isolation 隔離性
數據庫事務的隔離性要求數據庫中的事務不會受另一個并發執行的事務的影響,對于數據庫中同時執行的每個事務來說,其他事務要么還沒開始執行,要么已經執行結束,它都感覺不到還有別的事務正在執行。
D:durability 持久性
數據庫事務的持久性要求事務對數據庫的改變是永久的,哪怕數據庫發生損壞都不會影響到已發生的事務。
如果事務沒有完成,數據庫因故斷電了,那么重啟后也應該是沒有執行事務的狀態,如果事務已經完成后數據庫斷電了,那么重啟后就應該是事務執行完成后的狀態。
(14)什么是臟讀,不可重復讀和幻讀?
詳見數據庫的事務隔離級別總結
- 臟讀:臟讀是指一個事務在處理過程中讀取了另一個還沒提交的事務的數據。
比如A向B轉賬100,A的賬戶減少了100,而B的賬戶還沒來得及修改,此時一個并發的事務訪問到了B的賬戶,就是臟讀
- 不可重復讀:不可重復讀是對于數據庫中的某一個字段,一個事務多次查詢卻返回了不同的值,這是由于在查詢的間隔中,該字段被另一個事務修改并提交了。
比如A第一次查詢自己的賬戶有1000元,此時另一個事務給A的賬戶增加了1000元,所以A再次讀取他的賬戶得到了2000的結果,跟第一次讀取的不一樣。
不可重復讀與臟讀的不同之處在于,臟讀是讀取了另一個事務沒有提交的臟數據,不可重復讀是讀取了已經提交的數據,實際上并不是一個異常現象。 - 幻讀:事務多次讀取同一個范圍的時候,查詢結果的記錄數不一樣,這是由于在查詢的間隔中,另一個事務新增或刪除了數據。
比如A公司一共有100個人,第一次查詢總人數得到100條記錄,此時另一個事務新增了一個人,所以下一次查詢得到101條記錄。
不可重復度和幻讀的不同之處在于,幻讀是多次讀取的結果行數不同,不可重復度是讀取結果的值不同。
避免不可重復讀需要鎖行,避免幻讀則需要鎖表。
臟讀,不可重復讀和幻讀都是數據庫的讀一致性問題,是在并行的過程中出現的問題,必須采用一定的隔離級別解決。
詳見臟讀、不可重復讀和幻讀的區別
(15) 數據庫的隔離級別,mysql和Oracle的隔離級別分別是什么(重點)
詳見數據庫的事務隔離級別總結和數據庫隔離級別
為了保證數據庫事務一致性,解決臟讀,不可重復讀和幻讀的問題,數據庫的隔離級別一共有四種隔離級別:
- 讀未提交 Read Uncommitted: 最低級別的隔離,不能解決以上問題
- 讀已提交 Read committed: 可以避免臟讀的發生
- 可重復讀 Reapeatable read: 確保事務可以多次從一個字段中讀取相同的值,在該事務執行期間,禁止其他事務對此字段的更新,可以避免臟讀和不可重復讀。 通過鎖行來實現
- 串行化 Serializaion 最嚴格的事務隔離機制,要求所有事務被串行執行,可以避免以上所有問題。 通過鎖表來實現
Oracle的默認隔離級別是讀已提交,實現了四種隔離級別中的讀已提交和串行化隔離級別
MySQL的默認隔離級別是可重復讀,并且實現了所有四種隔離級別
(16) 數據庫連接池的作用
(17) Mysql的表空間方式,各自特點
- 共享表空間:指的是數據庫的所有的表數據,索引文件全部放在一個文件中,默認這個共享表空間的文件路徑在 data 目錄下。
- 獨立表空間:每一個表都將會生成以獨立的文件方式來進行存儲。 優點:當表被刪除時這部分空間可以被回收;可以更快的恢復和備份單個表;將單個表復制到另一個實例會很方便; 缺點:mysqld會維持很多文件句柄,表太多會影響性能。如果很多表都增長會導致碎片問題
(18) 分布式事務
(19) 數據庫的范式
https://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html
- 第一范式(確保每列保持原子性)
第一范式是最基本的范式。如果數據庫表中的所有字段值都是不可分解的原子值,就說明該數據庫表滿足了第一范式。
比如 學生 選課(包括很多課程) 就不符合第一范式
- 第二范式(確保表中的每列都和主鍵相關)
在滿足第一范式的前提下,(主要針對聯合主鍵而言)第二范式需要確保數據庫表中的每一列都和主鍵的所有成員直接相關,由整個主鍵才能唯一確定,而不能只與主鍵的某一部分相關或者不相關。
比如一張學生信息表,由主鍵(學號)可以唯一確定一個學生的姓名,班級,年齡等信息。但是主鍵 (學號,班級) 與列 姓名,班主任,教室 就不符合第二范式,因為班主任跟部分主鍵(班級)是依賴關系
- 第三范式(確保非主鍵的列沒有傳遞依賴)
在滿足第二范式的前提下,第三范式需要確保數據表中的每一列數據都和主鍵直接相關,而不能間接相關。非主鍵的列不能確定其他列,列與列之間不能出現傳遞依賴。
比如一張學生信息表,主鍵是(學號)列包括 姓名,班級,班主任 就不符合第三范式,因為非主鍵的列中 班主任 依賴于 班級
- BCNF范式(確保主鍵之間沒有傳遞依賴)
主鍵有可能是由多個屬性組合成的復合主鍵,那么多個主鍵之間不能有傳遞依賴。也就是復合主鍵之間誰也不能決定誰,相互之間沒有關系。
(20) 數據的鎖的種類,加鎖的方式
以MYSQL為例,
- 按照類型來分有樂觀鎖和悲觀鎖
- 根據粒度來分有行級鎖,頁級鎖,表級鎖(粒度一個比一個大) (僅BDB,Berkeley Database支持頁級鎖)
- 根據作用來分有共享鎖(讀鎖)和排他鎖(寫鎖)。
(21) 什么是共享鎖和排他鎖
-
共享鎖是讀操作的時候創建的鎖,一個事務對數據加上共享鎖之后,其他事務只能對數據再加共享鎖,不能進行寫操作直到釋放所有共享鎖。
-
排他鎖是寫操作時創建的鎖,事務對數據加上排他鎖之后其他任何事務都不能對數據加任何的鎖(即其他事務不能再訪問該數據)
https://blog.csdn.net/qq_42743933/article/details/81236658
(22) 分庫分表的理解和簡介
(23) MySQL常用的存儲引擎
存儲引擎就是指表的類型以及表在計算機上的存儲方式。
存儲引擎的概念是MySQL的特點,在MySQL中的存儲引擎有很多種,可以通過“SHOW ENGINES”語句來查看。下面重點關注InnoDB、MyISAM、MEMORY這三種。
1.InnoDB存儲引擎
InnoDB給MySQL的表提供了事務處理、回滾、崩潰修復能力和多版本并發控制的事務安全。MySQL的默認存儲引擎就是InnoDB。
InnoDB存儲引擎總支持AUTO_INCREMENT。自動增長列的值不能為空,并且值必須唯一。MySQL中規定自增列必須為主鍵。在插入值的時候,如果自動增長列不輸入值,則插入的值為自動增長后的值;如果輸入的值為0或空(NULL),則插入的值也是自動增長后的值;如果插入某個確定的值,且該值在前面沒有出現過,就可以直接插入。
InnoDB還支持外鍵(FOREIGN KEY)。外鍵所在的表叫做子表,外鍵所依賴(REFERENCES)的表叫做父表。父表中被字表外鍵關聯的字段必須為主鍵。當刪除、更新父表中的某條信息時,子表也必須有相應的改變,這是數據庫的參照完整性規則。
InnoDB中,創建的表的表結構存儲在.frm文件中(我覺得是frame的縮寫吧)。數據和索引存儲在innodb_data_home_dir和innodb_data_file_path定義的表空間中。
InnoDB的優勢在于提供了良好的事務處理、崩潰修復能力和并發控制。缺點是讀寫效率較差,占用的數據空間相對較大。
2.MyISAM存儲引擎
MyISAM是MySQL中常見的存儲引擎,曾經是MySQL的默認存儲引擎。MyISAM是基于ISAM引擎發展起來的,增加了許多有用的擴展。
MyISAM的表存儲成3個文件。文件的名字與表名相同。拓展名為frm、MYD、MYI。其實,frm文件存儲表的結構;MYD文件存儲數據,是MYData的縮寫;MYI文件存儲索引,是MYIndex的縮寫。
基于MyISAM存儲引擎的表支持3種不同的存儲格式。包括靜態型、動態型和壓縮型。其中,靜態型是MyISAM的默認存儲格式,它的字段是固定長度的;動態型包含變長字段,記錄的長度不是固定的;壓縮型需要用到myisampack工具,占用的磁盤空間較小。
MyISAM的優勢在于占用空間小,處理速度快。缺點是不支持事務的完整性和并發性。
3.MEMORY存儲引擎
MEMORY是MySQL中一類特殊的存儲引擎。它使用存儲在內存中的內容來創建表,而且數據全部放在內存中。這些特性與前面的兩個很不同。
每個基于MEMORY存儲引擎的表實際對應一個磁盤文件。該文件的文件名與表名相同,類型為frm類型。該文件中只存儲表的結構。而其數據文件,都是存儲在內存中,這樣有利于數據的快速處理,提高整個表的效率。值得注意的是,服務器需要有足夠的內存來維持MEMORY存儲引擎的表的使用。如果不需要了,可以釋放內存,甚至刪除不需要的表。
MEMORY默認使用哈希索引。速度比使用B型樹索引快。當然如果你想用B型樹索引,可以在創建索引時指定。
注意,MEMORY用到的很少,因為它是把數據存到內存中,如果內存出現異常就會影響數據。如果重啟或者關機,所有數據都會消失。因此,基于MEMORY的表的生命周期很短,一般是一次性的。
(24)數據庫高并發的解決方案
(25)樂觀鎖與悲觀鎖解釋一下
一般的數據庫都會支持并發操作,在并發操作中為了避免數據沖突,所以需要對數據上鎖,樂觀鎖和悲觀鎖就是兩種不同的上鎖方式。
悲觀鎖假設數據在并發操作中一定會發生沖突,所以在數據開始讀取的時候就把數據鎖住。而樂觀鎖則假設數據一般情況下不會發生沖突,所以在數據提交更新的時候,才會檢測數據是否有沖突。
(26)樂觀鎖與悲觀鎖是怎么實現的
悲觀鎖有行級鎖和頁級鎖兩種形式。行級鎖對正在使用的單條數據進行鎖定,事務完成后釋放該行數據,而頁級鎖則對整張表進行鎖定,事務正在對該表進行訪問的時候不允許其他事務并行訪問。
悲觀鎖要求在整個過程中一直與數據庫有一條連接,因為上一個事務完成后才能讓下一個事務執行,這個過程是串行的。
樂觀鎖有三種常用的實現形式:
- 一種是在執行事務時把整個數據都拷貝到應用中,在數據更新提交的時候比較數據庫中的數據與新數據,如果兩個數據一摸一樣則表示沒有沖突可以直接提交,如果有沖突就要交給業務邏輯去解決。
- 一種是使用版本戳來對數據進行標記,數據每發生一次修改,版本號就增加1。某條數據在提交的時候,如果數據庫中的版本號與自己的一致,就說明數據沒有發生修改,否則就認為是過期數據需要處理。
- 最后一種采用時間戳對數據最后修改的時間進行標記。與上一種類似。
(27)對數據庫目前最新技術有什么了解嗎
4. Linux
(0) Linux常用命令操作
- 找出關鍵字出現的次數
- 查詢一個文件重復最多的前10條記錄
- 一個文件夾目錄,獲取該目錄下ppt結尾的文件
- 按空格分割行數據取第一個
(1) Linux的I/O模型介紹以及同步異步阻塞非阻塞的區別(超級重要)
https://blog.csdn.net/sqsltr/article/details/92762279
https://www.cnblogs.com/euphie/p/6376508.html
(IO過程包括兩個階段:(1)內核從IO設備讀寫數據和(2)進程從內核復制數據)
-
阻塞:調用IO操作的時候,如果緩沖區空或者滿了,調用的進程或者線程就會處于阻塞狀態直到IO可用并完成數據拷貝。
-
非阻塞:調用IO操作的時候,內核會馬上返回結果,如果IO不可用,會返回錯誤,這種方式下進程需要不斷輪詢直到IO可用為止,但是當進程從內核拷貝數據時是阻塞的。
-
IO多路復用就是同時監聽多個描述符,一旦某個描述符IO就緒(讀就緒或者寫就緒),就能夠通知進程進行相應的IO操作,否則就將進程阻塞在select或者epoll語句上。
-
同步IO:同步IO模型包括阻塞IO,非阻塞IO和IO多路復用。特點就是當進程從內核復制數據的時候都是阻塞的。
-
異步IO:在檢測IO是否可用和進程拷貝數據的兩個階段都是不阻塞的,進程可以做其他事情,當IO完成后內核會給進程發送一個信號。
(2) 文件系統的理解(EXT4,XFS,BTRFS)
(3) EPOLL的介紹和了解
https://zhuanlan.zhihu.com/p/56486633
https://www.jianshu.com/p/397449cadc9a
https://blog.csdn.net/davidsguo008/article/details/73556811
Epoll是Linux進行IO多路復用的一種方式,用于在一個線程里監聽多個IO源,在IO源可用的時候返回并進行操作。它的特點是基于事件驅動,性能很高。
epoll將文件描述符拷貝到內核空間后使用紅黑樹進行維護,同時向內核注冊每個文件描述符的回調函數,當某個文件描述符可讀可寫的時候,將這個文件描述符加入到就緒鏈表里,并喚起進程,返回就緒鏈表到用戶空間,由用戶程序進行處理。
Epoll有三個系統調用:epoll_create(),epoll_ctl()和epoll_wait()。
-
eoll_create()函數在內核中初始化一個eventpoll對象,同時初始化紅黑樹和就緒鏈表。
-
epoll_ctl()用來對監聽的文件描述符進行管理。將文件描述符插入紅黑樹,或者從紅黑樹中刪除,這個過程的時間復雜度是log(N)。同時向內核注冊文件描述符的回調函數。
-
epoll_wait()會將進程放到eventpoll的等待隊列中,將進程阻塞,當某個文件描述符IO可用時,內核通過回調函數將該文件描述符放到就緒鏈表里,epoll_wait()會將就緒鏈表里的文件描述符返回到用戶空間。
(4) IO復用的三種方法(select,poll,epoll)深入理解,包括三者區別,內部原理實現?
(1)select的方法介紹:select把所有監聽的文件描述符拷貝到內核中,掛起進程。當某個文件描述符可讀或可寫的時候,中斷程序喚起進程,select將監聽的文件描述符再次拷貝到用戶空間,然select后遍歷這些文件描述符找到IO可用的文件。下次監控的時候需要再次拷貝這些文件描述符到內核空間。select支持監聽的描述符最大數量是1024.
(2)poll使用鏈表保存文件描述符,其他的跟select沒有什么不同。
(3)epoll將文件描述符拷貝到內核空間后使用紅黑樹進行維護,同時向內核注冊每個文件描述符的回調函數,當某個文件描述符可讀可寫的時候,將這個文件描述符加入到就緒鏈表里,并喚起進程,返回就緒鏈表到用戶空間。
詳見 https://www.cnblogs.com/Anker/p/3265058.html
(5) Epoll的ET模式和LT模式(ET的非阻塞)
- ET是邊緣觸發模式,在這種模式下,只有當描述符從未就緒變成就緒時,內核才會通過epoll進行通知。然后直到下一次變成就緒之前,不會再次重復通知。也就是說,如果一次就緒通知之后不對這個描述符進行IO操作導致它變成未就緒,內核也不會再次發送就緒通知。優點就是只通知一次,減少內核資源浪費,效率高。缺點就是不能保證數據的完整,有些數據來不及讀可能就會無法取出。
- LT是水平觸發模式,在這個模式下,如果文件描述符IO就緒,內核就會進行通知,如果不對它進行IO操作,只要還有未操作的數據,內核都會一直進行通知。優點就是可以確保數據可以完整輸出。缺點就是由于內核會一直通知,會不停從內核空間切換到用戶空間,資源浪費嚴重。
(6) 查詢進程占用CPU的命令(注意要了解到used,buf,代表意義)
詳見:https://blog.csdn.net/qq_36357820/article/details/76606113
(7) linux的其他常見命令(kill,find,cp等等)
(8) shell腳本用法
(9) 硬連接和軟連接的區別
(10) 文件權限怎么看(rwx)
(11) 文件的三種時間(mtime, atime,ctime),分別在什么時候會改變
(12) Linux監控網絡帶寬的命令,查看特定進程的占用網絡資源情況命令
(13)Linux中線程的同步方式有哪些?
(14)怎么修改一個文件的權限
chmod 777 (177 277 477 等,權限組合是 1 2 4,分別代表r x w )
(15)查看文件內容常用命令
詳見: http://blog.sina.com.cn/s/blog_7b4ce6b101018l8l.html
這個用的太普遍了,主要是用于編輯。
(16)怎么找出含有關鍵字的前后4行
(17)Linux的GDB調試
(18)coredump是什么 怎么才能coredump
coredump是程序由于異常或者bug在運行時異常退出或者終止,在一定的條件下生成的一個叫做core的文件,這個core文件會記錄程序在運行時的內存,寄存器狀態,內存指針和函數堆棧信息等等。對這個文件進行分析可以定位到程序異常的時候對應的堆棧調用信息。
coredump產生的條件
(19)tcpdump常用命令
用簡單的話來定義tcpdump,就是:dump the traffic on a network,根據使用者的定義對網絡上的數據包進行截獲的包分析工具。 tcpdump可以將網絡中傳送的數據包的“頭”完全截獲下來提供分析。它支持針對網絡層、協議、主機、網絡或端口的過濾,并提供and、or、not等邏輯語句來幫助你去掉無用的信息。
實用命令實例
將某端口收發的數據包保存到文件
sudo tcpdump -i any port 端口 -w 文件名.cap
打印請求到屏幕
sudo tcpdump -i any port 端口 -Xnlps0
默認啟動
tcpdump
普通情況下,直接啟動tcpdump將監視第一個網絡接口上所有流過的數據包。
監視指定網絡接口的數據包
tcpdump -i eth1
如果不指定網卡,默認tcpdump只會監視第一個網絡接口,一般是eth0,下面的例子都沒有指定網絡接口。
(20) crontab命令
詳見:https://www.cnblogs.com/peida/archive/2013/01/08/2850483.html
corntab命令是用來指定用戶計劃任務的。用戶將需要定時執行的任務寫入crontab文件中,提交給crond進程定期執行。
- crontab命令用來對crontab文件進行管理
- crontab文件內容
crond是Linux下的周期性執行系統任務的守護進程,他會根據/etc下的crontab配置文件的內容執行。用戶需要將計劃任務寫入crontab文件中才能執行。
用戶所建立的crontab文件中,每一行都代表一項任務,每行的每個字段代表一項設置,它的格式共分為六個字段,前五段是時間設定段,第六段是要執行的命令段,格式如下:
minute hour day month week command其中: minute: 表示分鐘,可以是從0到59之間的任何整數。 hour:表示小時,可以是從0到23之間的任何整數。 day:表示日期,可以是從1到31之間的任何整數。 month:表示月份,可以是從1到12之間的任何整數。 week:表示星期幾,可以是從0到7之間的任何整數,這里的0或7代表星期日。 command:要執行的命令,可以是系統命令,也可以是自己編寫的腳本文件。 在以上各個字段中,還可以使用以下特殊字符: 星號(*):代表所有可能的值,例如month字段如果是星號,則表示在滿足其它字段的制約條件后每月都執行該命令操作。 逗號(,):可以用逗號隔開的值指定一個列表范圍,例如,“1,2,5,7,8,9” 中杠(-):可以用整數之間的中杠表示一個整數范圍,例如“2-6”表示“2,3,4,5,6” 正斜線(/):可以用正斜線指定時間的間隔頻率,例如“0-23/2”表示每兩小時執行一次。同時正斜線可以和星號一起使用,例如*/10,如果用在minute字段,表示每十分鐘執行一次。(21) 查看后臺進程
- jobs
查看當前控制臺的后臺進程
想要停止后臺進程,使用jobs命令查看其進程號(比如為num),然后kill %num即可
- ps
查看后臺進程
- top
查看所有進程和資源使用情況,類似Windows中的任務管理器
停止進程:界面是交互式的,在窗口輸入k 之后輸入PID,會提示輸入停止進程模式 有SIGTERM和 SIGKILL 如果留空不輸入,就是SIGTERM(優雅停止)
退出top:輸入q即可
5. 操作系統
(1) 進程與線程的區別和聯系(重點)
- 區別
- 聯系 進程與線程之間的關系:線程是存在進程的內部,一個進程中可以有多個線程,一個線程只能存在一個進程中。
(2) Linux理論上最多可以創建多少個進程?一個進程可以創建多少線程,和什么有關
答:32768. 因為進程的pid是用pid_t來表示的,pid_t的最大值是32768.所以理論上最多有32768個進程。
至于線程。進程最多可以創建的線程數是根據分配給調用棧的大小,以及操作系統(32位和64位不同)共同決定的。Linux32位下是300多個。
(3) 馮諾依曼結構有哪幾個模塊?分別對應現代計算機的哪幾個部分?(百度安全一面)
- 存儲器:內存
- 控制器:南橋北橋
- 運算器:CPU
- 輸入設備:鍵盤
- 輸出設備:顯示器、網卡
(4) 進程之間的通信方法有哪幾種 (重點)
進程之間的通信方式主要有六種,包括管道,信號量,消息隊列,信號,共享內存,套接字。
-
管道:管道是半雙工的,雙方需要通信的時候,需要建立兩個管道。管道的實質是一個內核緩沖區,進程以先進先出的方式從緩沖區存取數據:管道一端的進程順序地將進程數據寫入緩沖區,另一端的進程則順序地讀取數據,該緩沖區可以看做一個循環隊列,讀和寫的位置都是自動增加的,一個數據只能被讀一次,讀出以后再緩沖區都不復存在了。當緩沖區讀空或者寫滿時,有一定的規則控制相應的讀進程或寫進程是否進入等待隊列,當空的緩沖區有新數據寫入或慢的緩沖區有數據讀出時,就喚醒等待隊列中的進程繼續讀寫。管道是最容易實現的
匿名管道pipe和命名管道除了建立,打開,刪除的方式不同外,其余都是一樣的。匿名管道只允許有親緣關系的進程之間通信,也就是父子進程之間的通信,命名管道允許具有非親緣關系的進程間通信。
管道的底層實現 https://segmentfault.com/a/1190000009528245
-
信號量:信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。信號量只有等待和發送兩種操作。等待(P(sv))就是將其值減一或者掛起進程,發送(V(sv))就是將其值加一或者將進程恢復運行。
-
信號:信號是Linux系統中用于進程之間通信或操作的一種機制,信號可以在任何時候發送給某一進程,而無須知道該進程的狀態。如果該進程并未處于執行狀態,則該信號就由內核保存起來,知道該進程恢復執行并傳遞給他為止。如果一個信號被進程設置為阻塞,則該信號的傳遞被延遲,直到其阻塞被取消時才被傳遞給進程。 信號是開銷最小的
-
共享內存:共享內存允許兩個或多個進程共享一個給定的存儲區,這一段存儲區可以被兩個或兩個以上的進程映射至自身的地址空間中,就像由malloc()分配的內存一樣使用。一個進程寫入共享內存的信息,可以被其他使用這個共享內存的進程,通過一個簡單的內存讀取讀出,從而實現了進程間的通信。共享內存的效率最高,缺點是沒有提供同步機制,需要使用鎖等其他機制進行同步。
-
消息隊列:消息隊列就是一個消息的鏈表,是一系列保存在內核中消息的列表。用戶進程可以向消息隊列添加消息,也可以向消息隊列讀取消息。
消息隊列與管道通信相比,其優勢是對每個消息指定特定的消息類型,接收的時候不需要按照隊列次序,而是可以根據自定義條件接收特定類型的消息。
可以把消息看做一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的進程可以向消息隊列中按照一定的規則添加新消息,對消息隊列有讀權限的進程可以從消息隊列中讀取消息。 -
套接字:套接口也是一種進程間通信機制,與其他通信機制不同的是,它可用于不同設備及其間的進程通信。
(5) 進程調度方法詳細介紹
https://blog.csdn.net/u011080472/article/details/51217754
https://blog.csdn.net/leex_brave/article/details/51638300
- 先來先服務 (FCFS first come first serve):按照作業到達任務隊列的順序調度 FCFS是非搶占式的,易于實現,效率不高,性能不好,有利于長作業(CPU繁忙性)而不利于短作業(I/O繁忙性)。
- 短作業優先 (SHF short job first):每次從隊列里選擇預計時間最短的作業運行。SJF是非搶占式的,優先照顧短作業,具有很好的性能,降低平均等待時間,提高吞吐量。但是不利于長作業,長作業可能一直處于等待狀態,出現饑餓現象;完全未考慮作業的優先緊迫程度,不能用于實時系統。
- 最短剩余時間優先 該算法首先按照作業的服務時間挑選最短的作業運行,在該作業運行期間,一旦有新作業到達系統,并且該新作業的服務時間比當前運行作業的剩余服務時間短,則發生搶占;否則,當前作業繼續運行。該算法確保一旦新的短作業或短進程進入系統,能夠很快得到處理。
- 高響應比優先調度算法(Highest Reponse Ratio First, HRRF)是非搶占式的,主要用于作業調度。基本思想:每次進行作業調度時,先計算后備作業隊列中每個作業的響應比,挑選最高的作業投入系統運行。響應比 = (等待時間 + 服務時間) / 服務時間 = 等待時間 / 服務時間 + 1。因為每次都需要計算響應比,所以比較耗費系統資源。
- 時間片輪轉 用于分時系統的進程調度。基本思想:系統將CPU處理時間劃分為若干個時間片(q),進程按照到達先后順序排列。每次調度選擇隊首的進程,執行完1個時間片q后,計時器發出時鐘中斷請求,該進程移至隊尾。以后每次調度都是如此。該算法能在給定的時間內響應所有用戶的而請求,達到分時系統的目的。
- 多級反饋隊列(Multilevel Feedback Queue)
(6) 進程的執行過程是什么樣的,執行一個進程需要做哪些工作?
進程的執行需要經過三大步驟:編譯,鏈接和裝入。
- 編譯:將源代碼編譯成若干模塊
- 鏈接:將編譯后的模塊和所需要的庫函數進行鏈接。鏈接包括三種形式:靜態鏈接,裝入時動態鏈接(將編譯后的模塊在鏈接時一邊鏈接一邊裝入),運行時動態鏈接(在執行時才把需要的模塊進行鏈接)
- 裝入:將模塊裝入內存運行
https://blog.csdn.net/qq_38623623/article/details/78306498
將進程裝入內存時,通常使用分頁技術,將內存分成固定大小的頁,進程分為固定大小的塊,加載時將進程的塊裝入頁中,并使用頁表記錄。減少外部碎片。
通常操作系統還會使用虛擬內存的技術將磁盤作為內存的擴充。
(6) 操作系統的內存管理說一下
https://www.cnblogs.com/peterYong/p/6556619.html
https://zhuanlan.zhihu.com/p/141602175
操作系統的內存管理包括物理內存管理和虛擬內存管理
- 物理內存管理包括交換與覆蓋,分頁管理,分段管理和段頁式管理等;
- 虛擬內存管理包括虛擬內存的概念,頁面置換算法,頁面分配策略等;
(面試官這樣問的時候,其實是希望你能講講虛擬內存)
(7) 實現一個LRU算法
用到兩個數據結構:哈希+雙向鏈表
unordered_map<int,list<pair<int,int> > > cache ;// 存放鍵,迭代器 list<pair<int,int>> auxlist; // 存放 <鍵,值> class LRUCache {int cap;list<pair<int,int>> l;// front:new back:old 存放值 新的放前面,因為前面的可以取得有效的迭代器map<int,list<pair<int,int> >::iterator > cache;// 存放鍵,迭代器 public:LRUCache(int capacity) {cap=capacity;}int get(int key) {auto mapitera = cache.find(key);if(mapitera==cache.end()){return -1;}else{// foundlist<pair<int,int>>::iterator listItera = mapitera->second;int value = (*listItera).second;l.erase(listItera);l.push_front({key,value});cache[key]=l.begin();return value;}}void put(int key, int value) {auto itera = cache.find(key);if(itera!=cache.end()){// existlist<pair<int,int>>::iterator listItera = itera->second;l.erase(listItera);l.push_front({key,value});cache[key]=l.begin();}else{// not existif(cache.size()>=cap){pair<int,int> oldpair = l.back();l.pop_back();cache.erase(oldpair.first);}l.push_front({key,value});cache[key]=l.begin();}} };/*** Your LRUCache object will be instantiated and called as such:* LRUCache* obj = new LRUCache(capacity);* int param_1 = obj->get(key);* obj->put(key,value);*/(8) 死鎖產生的必要條件(怎么檢測死鎖,解決死鎖問題)
(1) 互斥:一個資源每次只能被一個進程使用。
(2) 占有并請求:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不可剝奪:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待:若干進程之間形成一種頭尾相接的循環等待資源關系。
產生死鎖的原因主要是:
(1) 因為系統資源不足。
(2) 進程運行推進的順序不合適。
(3) 資源分配不當等。
(8) 死鎖的恢復
(1) 一次性全部終止;(2) 逐步終止(優先級,代價函數)
(1) 逐步剝奪:一次剝奪死鎖進程所占有的一個或一組資源,如果死鎖尚未解除再繼續剝奪,直至死鎖解除為止。
(2) 一次剝奪:一次性地剝奪死鎖進程所占有的全部資源。
(1) 要實現“回退”,必須“記住”以前某一點處的現場,而現場隨著進程推進而動態變化,需要花費大量時間和空間。
(2) 一個回退的進程應當“挽回”它在回退點之間所造成的影響,如修改某一文件,給其它進程發送消息等,這些在實現時是難以做到的
(8)什么是饑餓
饑餓是由于資源分配策略不公引起的,當進程或線程無法訪問它所需要的資源而不能繼續執行時,就會發生饑餓現象。
(9) 如果要你實現一個mutex互斥鎖你要怎么實現?
https://blog.csdn.net/kid551/article/details/84338619
實現mutex最重要的就是實現它的lock()方法和unlock()方法。我們保存一個全局變量flag,flag=1表明該鎖已經鎖住,flag=0表明鎖沒有鎖住。
實現lock()時,使用一個while循環不斷檢測flag是否等于1,如果等于1就一直循環。然后將flag設置為1;unlock()方法就將flag置為0;
因為while有可能被重入,所以可以用TestandSet()方法。
int TestAndSet(int *ptr, int new) {int old = *ptr;*ptr = new;return old; }(10)線程之間的通信方式有哪些? 進程與線程同步?
線程之間通信:
- 使用全局變量
- 使用信號機制
- 使用事件
進程與線程的同步
- 進程:無名管道、有名管道、信號、共享內存、消息隊列、信號量
- 線程:互斥量、讀寫鎖、自旋鎖、線程信號、條件變量
(13) 什么時候用多進程,什么時候用多線程
https://blog.csdn.net/yu876876/article/details/82810178
- 頻繁修改:需要頻繁創建和銷毀的優先使用多線程
- 計算量:需要大量計算的優先使用多線程 因為需要消耗大量CPU資源且切換頻繁,所以多線程好一點
- 相關性:任務間相關性比較強的用多線程,相關性比較弱的用多進程。因為線程之間的數據共享和同步比較簡單。
- 多分布:可能要擴展到多機分布的用多進程,多核分布的用多線程。
但是實際中更常見的是進程加線程的結合方式,并不是非此即彼的。
(14) 文件讀寫使用的系統調用
(15) 孤兒進程和僵尸進程分別是什么,怎么形成的?
-
僵尸進程
定義:一個進程使用fork創建子進程,如果子進程退出,而父進程并沒有調用wait或者waitpid獲取子進程的狀態信息,那么子進程的進程描述符等一系列信息還會保存在系統中。這種進程稱之為僵死進程。
危害:在Unix系統管理中,當用ps命令觀察進程的執行狀態時,經常看到某些進程的狀態欄為defunct,這就是所謂的“僵尸”進程。“僵尸”進程是一個早已死亡的進程,但在進程表(processs table)中仍占了一個位置(slot)。由于進程表的容量是有限的,所以,defunct進程不僅占用系統的內存資源,影響系統的性能,而且如果其數目太多,還會導致系統癱瘓。
處理方法:
改寫父進程,在子進程死后要為它收尸。具體做法是接管SIGCHLD信號。子進程死后,會發送SIGCHLD信號給父進程,父進程收到此信號后,執行waitpid()函數為子進程收尸。這是基于這樣的原理:就算父進程沒有調用wait,內核也會向它發送SIGCHLD消息,盡管默認處理是忽略,如果想響應這個消息,可以設置一個處理函數。
把父進程殺掉。父進程死后,僵尸進程成為”孤兒進程”,過繼給1號進程init,init始終會負責清理僵尸進程.它產生的所有僵尸進程也跟著消失。 -
孤兒進程
父進程運行結束,但子進程還在運行(未運行結束)的子進程就稱為孤兒進程。孤兒進程最終會被init進程(進程號為1)所收養,因此init進程此時變成孤兒進程的父進程,并由init進程對它們完成狀態收集工作。(linux下,init是內核啟動的第一個用戶級進程,init有許多很重要的任務,比如像啟動getty(用于用戶登錄)、實現運行級別、以及處理孤立進程。)
(16) 說一下PCB/說一下進程地址空間/
https://blog.csdn.net/qq_38499859/article/details/80057427
PCB就是進程控制塊,是操作系統中的一種數據結構,用于表示進程狀態,操作系統通過PCB對進程進行管理。
PCB中包含有:進程標識符,處理器狀態,進程調度信息,進程控制信息
進程地址空間內有:
- 代碼段text:存放程序的二進制代碼
- 初始化的數據Data:已經初始化的變量和數據
- 未初始化的數據BSS:還沒有初始化的數據
- 棧
- 堆
(17) 內核空間和用戶空間是怎樣區分的
在Linux中虛擬地址空間范圍為0到4G,最高的1G地址(0xC0000000到0xFFFFFFFF)供內核使用,稱為內核空間,低的3G空間(0x00000000到0xBFFFFFFF)供各個進程使用,就是用戶空間。
內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據。
(18) 多線程是如何同步的(尤其是如果項目中用到了多線程,很大可能會結合討論)
https://blog.csdn.net/s_lisheng/article/details/74278765
- 臨界區
- 信號量
- 事件
- 互斥量
(19) 同一個進程內的線程會共享什么資源?
- 該進程的地址空間
- 全局變量
- 堆空間
線程的棧空間是自己獨有的
(20) 異常和中斷的區別
(21) 一般情況下在Linux/windows平臺下棧空間的大小
在Linux下棧空間通常是8M,Windows下是1M
(22)虛擬內存的了解
https://www.cnblogs.com/Przz/p/6876988.html
在運行一個進程的時候,它所需要的內存空間可能大于系統的物理內存容量。通常一個進程會有4G的空間,但是物理內存并沒有這么大,所以這些空間都是虛擬內存,它的地址都是邏輯地址,每次在訪問的時候都需要映射成物理地址。
當進程訪問某個邏輯地址的時候,會去查看頁表,如果頁表中沒有相應的物理地址,說明內存中沒有這頁的數據,發生缺頁異常,這時候進程需要把數據從磁盤拷貝到物理內存中。如果物理內存已經滿了,就需要覆蓋已有的頁,如果這個頁曾經被修改過,那么還要把它寫回磁盤。
(23)服務器高并發的解決方案
應用數據與靜態資源分離
將靜態資源(圖片,視頻,js,css等)單獨保存到專門的靜態資源服務器中,在客戶端訪問的時候從靜態資源服務器中返回靜態資源,從主服務器中返回應用數據。
客戶端緩存
因為效率最高,消耗資源最小的就是純靜態的html頁面,所以可以把網站上的頁面盡可能用靜態的來實現,在頁面過期或者有數據更新之后再將頁面重新緩存。或者先生成靜態頁面,然后用ajax異步請求獲取動態數據。
集群和分布式
(集群是所有的服務器都有相同的功能,請求哪臺都可以,主要起分流作用)
(分布式是將不同的業務放到不同的服務器中,處理一個請求可能需要使用到多臺服務器,起到加快請求處理的速度。)
可以使用服務器集群和分布式架構,使得原本屬于一個服務器的計算壓力分散到多個服務器上。同時加快請求處理的速度。
反向代理
在訪問服務器的時候,服務器通過別的服務器獲取資源或結果返回給客戶端。
(24)協程了解嗎(高頻)
協程和微線程是一個東西。
協程就是子程序在執行時中斷并轉去執行別的子程序,在適當的時候又返回來執行。
這種子程序間的跳轉不是函數調用,也不是多線程執行,所以省去了線程切換的開銷,效率很高,并且不需要多線程間的鎖機制,不會發生變量寫沖突。
(25)那協程的底層是怎么實現的,怎么使用協程?
協程進行中斷跳轉時將函數的上下文存放在其他位置中,而不是存放在函數堆棧里,當處理完其他事情跳轉回來的時候,取回上下文繼續執行原來的函數。
(26)進程的狀態以及轉換圖
-
三態模型
三態模型包括三種狀態: - 執行:進程分到CPU時間片,可以執行
- 就緒:進程已經就緒,只要分配到CPU時間片,隨時可以執行
- 阻塞:有IO事件或者等待其他資源
-
五態模型
- 新建態:進程剛剛創建。
- 就緒態:
- 運行態:
- 等待態:出現等待事件
- 終止態:進程結束
-
七態模型
- 新建態
- 就緒掛起態
- 就緒態
- 運行態
- 等待態
- 掛起等待態
- 終止態
(27)在執行malloc申請內存的時候,操作系統是怎么做的?/內存分配的原理說一下/malloc函數底層是怎么實現的?/進程是怎么分配內存的?
https://blog.csdn.net/yusiguyuan/article/details/39496057
從操作系統層面上看,malloc是通過兩個系統調用來實現的: brk和mmap
- brk是將進程數據段(.data)的最高地址指針向高處移動,這一步可以擴大進程在運行時的堆大小
- mmap是在進程的虛擬地址空間中尋找一塊空閑的虛擬內存,這一步可以獲得一塊可以操作的堆內存。
通常,分配的內存小于128k時,使用brk調用來獲得虛擬內存,大于128k時就使用mmap來獲得虛擬內存。
進程先通過這兩個系統調用獲取或者擴大進程的虛擬內存,獲得相應的虛擬地址,在訪問這些虛擬地址的時候,通過缺頁中斷,讓內核分配相應的物理內存,這樣內存分配才算完成。
(28)什么是字節序?怎么判斷是大端還是小端?有什么用?
https://www.cnblogs.com/broglie/p/5645200.html
字節序是對象在內存中存儲的方式,大端即為最高有效位在前面,小端即為最低有效位在前面。
判斷大小端的方法:使用一個union數據結構
在網絡編程中不同字節序的機器發送和接收的順序不同。
6. 場景題/算法題
(0) leetcode hot100至少刷兩遍,劍指offer至少刷兩遍 重中之重!!
面試中90%的算法題都從leetcode hot100和劍指offer中出 刷兩遍非常有必要
(1) 介紹熟悉的設計模式(單例,簡單工廠模式)
(2) 寫單例模式,線程安全版本
class Singleton { public:~Singleton() { std::cout << "destructor called!" << std::endl; }Singleton(const Singleton &) = delete;Singleton &operator=(const Singleton &) = delete;static Singleton &get_instance(){static Singleton instance;return instance;}private:Singleton() { std::cout << "constructor called!" << std::endl; } }; int main(int argc, char *argv[]) {Singleton &instance_1 = Singleton::get_instance();Singleton &instance_2 = Singleton::get_instance();return 0; }(3) 寫三個線程交替打印ABC
#include<iostream> #include<thread> #include<mutex> #include<condition_variable> using namespace std;mutex mymutex; condition_variable cv; int flag=0;void printa(){unique_lock<mutex> lk(mymutex);int count=0;while(count<10){while(flag!=0) cv.wait(lk);cout<<"thread 1: a"<<endl;flag=1;cv.notify_all();count++;}cout<<"my thread 1 finish"<<endl; } void printb(){unique_lock<mutex> lk(mymutex);for(int i=0;i<10;i++){while(flag!=1) cv.wait(lk);cout<<"thread 2: b"<<endl;flag=2;cv.notify_all();}cout<<"my thread 2 finish"<<endl; } void printc(){unique_lock<mutex> lk(mymutex);for(int i=0;i<10;i++){while(flag!=2) cv.wait(lk);cout<<"thread 3: c"<<endl;flag=0;cv.notify_all();}cout<<"my thread 3 finish"<<endl; } int main(){thread th2(printa);thread th1(printb);thread th3(printc);th1.join();th2.join();th3.join();cout<<" main thread "<<endl;}(4) 二維碼登錄的實現過程 場景題
(5) 不使用臨時變量實現swap函數
- 使用異或/加減等方式,下面給出使用異或的實現方法
(6) 實現一個strcpy函數(或者memcpy),如果內存可能重疊呢
(7) 實現快排
int partition(vector<int>& A,int left,int right) {int i=left;for(int j=left;j<=right-1;j++){if(A[j]<=A[right]){swap(A[i],A[j]);i++;}}swap(A[i],A[right]);return i; } void quicksort(vector<int>& A,int left,int right) {if(right<=left)return;int q = partition(A, left, right);quicksort(A,left,q-1);quicksort(A,q+1,right); }(8) 實現一個堆排序
堆排序的基本過程:
- 將n個元素的序列構建一個大頂堆或小頂堆
- 將堆頂的元素放到序列末尾
- 將前n-1個元素重新構建大頂堆或小頂堆,重復這個過程,直到所有元素都已經排序
整體時間復雜度為nlogn
#include<iostream> #include<vector> using namespace std; void swap(vector<int>& arr, int a,int b){arr[a]=arr[a]^arr[b];arr[b]=arr[a]^arr[b];arr[a]=arr[a]^arr[b]; } void adjust(vector<int>& arr,int len,int index){int maxid=index;// 計算左右子節點的下標 left=2*i+1 right=2*i+2 parent=(i-1)/2int left=2*index+1,right=2*index+2;// 尋找當前以index為根的子樹中最大/最小的元素的下標if(left<len and arr[left]<arr[maxid]) maxid=left;if(right<len and arr[right]<arr[maxid]) maxid=right;// 進行交換,記得要遞歸進行adjust,傳入的index是maxidif(maxid!=index){swap(arr,maxid,index);adjust(arr,len,maxid);} } void heapsort(vector<int>&arr,int len){// 初次構建堆,i要從最后一個非葉子節點開始,所以是(len-1-1)/2,0這個位置要加等號for(int i=(len-1-1)/2;i>=0;i--){adjust(arr,len,i);}// 從最后一個元素的下標開始往前遍歷,每次將堆頂元素交換至當前位置,并且縮小長度(i為長度),從0處開始adjustfor(int i=len-1;i>0;i--){swap(arr,0,i);adjust(arr,i,0);// 注意每次adjust是從根往下調整,所以這里index是0!} } int main(){vector<int> arr={3,4,2,1,5,8,7,6};cout<<"before: "<<endl;for(int item:arr) cout<<item<<" ";cout<<endl;heapsort(arr,arr.size());cout<<"after: "<<endl;for(int item:arr)cout<<item<<" ";cout<<endl;return 0; }(8) 實現一個插入排序
https://blog.csdn.net/left_la/article/details/8656425
void insertSort(vector<int>& nums){int len=nums.size();for(int i=1;i<len;i++){int key=nums[i];int j=i-1;while(j>=0 and nums[j]>key){nums[j+1]=nums[j];j--;}nums[j+1]=key;} }(9) 快排存在的問題,如何優化
- 3 種快排基準選擇方法:
隨機(rand函數)、固定(隊首、隊尾)、三數取中(隊首、隊中和隊尾的中間數)
- 4種優化方式:
優化1:當待排序序列的長度分割到一定大小后,使用插入排序
優化2:在一次分割結束后,可以把與Key相等的元素聚在一起,繼續下次分割時,不用再對與key相等元素分割
優化3:優化遞歸操作
優化4:使用并行或多線程處理子序列
(10) 反轉一個鏈表(招銀網絡二面)
ListNode* reverse(ListNode* root){ListNode* pre=nullptr,cur=root,nxt;while(cur!=nullptr){nxt=cur->next;cur->next=pre;pre=cur;cur=nxt;}return pre; }(11) Top K問題(可以采取的方法有哪些,各自優點?)(重點)
Top K 問題的常見形式:
給定10000個整數,找第K大(第K小)的數
給定10000個整數,找出最大(最小)的前K個數
給定100000個單詞,求前K詞頻的單詞
解決Top K問題若干種方法
- 使用最大最小堆。求最大的數用最小堆,求最小的數用最大堆。
- Quick Select算法。使用類似快排的思路,根據pivot劃分數組。
- 使用排序方法,排序后再尋找top K元素。
- 使用選擇排序的思想,對前K個元素部分排序。
- 將1000…個數分成m組,每組尋找top K個數,得到m×K個數,在這m×k個數里面找top K個數。
按順序掃描這10000個數,先取出K個元素構建一個大小為K的最小堆。每掃描到一個元素,如果這個元素大于堆頂的元素(這個堆最小的一個數),就放入堆中,并刪除堆頂的元素,同時整理堆。如果這個元素小于堆頂的元素,就直接pass。最后堆中剩下的元素就是最大的前Top K個元素,最右的葉節點就是Top 第K大的元素。
note:最小堆的插入時間復雜度為log(n),n為堆中元素個數,在這里是K。最小堆的初始化時間復雜度是nlog(n)
C++中的最大最小堆要用標準庫的priority_queue來實現。
struct Node {int value;int idx;Node (int v, int i): value(v), idx(i) {}friend bool operator < (const struct Node &n1, const struct Node &n2) ; };inline bool operator < (const struct Node &n1, const struct Node &n2) {return n1.value < n2.value; }priority_queue<Node> pq; // 此時pq為最大堆Quick Select脫胎于快速排序,提出這兩個算法的都是同一個人。算法的過程是這樣的:
首先選取一個樞軸,然后將數組中小于該樞軸的數放到左邊,大于該樞軸的數放到右邊。
此時,如果左邊的數組中的元素個數大于等于K,則第K大的數肯定在左邊數組中,繼續對左邊數組執行相同操作;
如果左邊的數組元素個數等于K-1,則第K大的數就是pivot;
如果左邊的數組元素個數小于K,則第K大的數肯定在右邊數組中,對右邊數組執行相同操作。
這個算法與快排最大的區別是,每次劃分后只處理左半邊或者右半邊,而快排在劃分后對左右半邊都繼續排序。
//此為Java實現 public int findKthLargest(int[] nums, int k) {return quickSelect(nums, k, 0, nums.length - 1); }// quick select to find the kth-largest element public int quickSelect(int[] arr, int k, int left, int right) {if (left == right) return arr[right];int index = partition(arr, left, right);if (index - left + 1 > k)return quickSelect(arr, k, left, index - 1);else if (index - left + 1 == k)return arr[index];elsereturn quickSelect(arr, k - (index - left + 1), index + 1, right);}掃描一遍數組,選出最大的一個元素,然后再掃描一遍數組,找出第二大的元素,再掃描一遍數組,找出第三大的元素。。。。。以此類推,找K個元素,時間復雜度為O(N*K)
(12) 8G的int型數據,計算機的內存只有2G,怎么對它進行排序?(外部排序)(百度一面)
我們可以使用外部排序來對它進行處理。首先將整個文件分成許多份,比如說m份,劃分的依據就是使得每一份的大小都能放到內存里。然后我們用快速排序或者堆排序等方法對每一份數據進行一個內部排序,變成有序子串。接著對這m份有序子串進行m路歸并排序。取這m份數據的最小元素,進行排序,輸出排序后最小的元素到結果中,同時從該元素所在子串中讀入一個元素,直到所有數據都被輸出到結果中為止。
https://blog.csdn.net/ailunlee/article/details/84548950
(13) 自己構建一棵二叉樹,使用帶有null標記的前序遍歷序列
在寫二叉樹相關算法的時候,如果需要自己構造測試用例(自己構造一棵二叉樹),往往是一件很麻煩的事情,我們可以用一個帶有null標記的前序遍歷序列來進行構造。 需要注意的是vec2tree()參數中的start是引用傳遞,而不是簡單的參數值傳遞。
#include<iostream> #include<vector> #include<queue> using namespace std;struct treeNode{string val;treeNode* left,*right;treeNode(string val):val(val){left=nullptr;right=nullptr;} };treeNode* vec2tree(vector<string>& vec,int& start){treeNode* root;if(vec[start]=="null"){start+=1;root=nullptr;}else{root=new treeNode(vec[start]);start+=1;root->left=vec2tree(vec,start);root->right=vec2tree(vec,start);}return root; }void tree2vec(treeNode *root,vector<string>& vec){if(root==nullptr){vec.push_back("null");}else{vec.push_back(root->val);tree2vec(root->left,vec);tree2vec(root->right,vec);} }int main(){vector<string> vec={"2","4","5","7","null","null","null","null","3","6","null","null","2","null","null"};int index=0,&start=index;treeNode* root=vec2tree(vec,start);//displaytree(root);vector<string> mvec;tree2vec(root,mvec);for(string item:mvec) cout<<item<<" ";cout<<endl;return 0;(14) 介紹一下b樹和它的應用場景有哪些
B樹也叫做B-樹,或者平衡多路樹,它是每個節點最多有m個子樹的平衡樹。一個m階的B樹具有如下幾個特征:
b樹主要應用于文件系統中,在數據庫中(mongoDB)也有應用,與B+樹相比好處應該是有時不需要訪問到葉節點就可以獲取數據。
查詢時間復雜度是logN
(15) 介紹一下b+樹和它的應用場景有哪些
B+樹是一種特殊的B樹,它把數據都存儲在葉子節點,并且葉節點間有指針連接。內部只存關鍵字(其中葉子節點的最小值作為索引)和孩子指針,簡化了內部節點。
應用場景主要是數據庫的索引
查詢時間復雜度也是logN
https://zhuanlan.zhihu.com/p/110202102
https://blog.csdn.net/hguisu/article/details/7786014
(16) 介紹一下紅黑樹和它的應用場景有哪些
紅黑樹是一種特殊的二叉查找樹,它在每一個節點上都使用紅色或黑色進行標記,通過一些性質確保它是始終平衡的。
它的性質是這樣的:
紅黑樹的插入,查詢,刪除在一般情況和最壞情況下的時間復雜度都是O(log(n))
應用場景主要是STL中map,set的實現,優點在于支持頻繁的修改,因為查詢刪除插入時間復雜度都是logN
(17) 怎么寫sql取表的前1000行數據(招銀網絡二面)
select * limit 1000 from t1(18) N個骰子出現和為m的概率
(19) 海量數據問題(可參考左神的書)
(20) 一致性哈希
(21)希爾排序說一下/手撕
https://www.cnblogs.com/chengxiao/p/6104371.html
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。
(22)Dijkstra算法說一下
(23)實現一個動態數組要怎么實現,說思路(騰訊teg一面)
模擬STL中vector的實現即可,去看一下vector的源碼。
(24)最小生成樹算法說一下
(25) 海量數據的bitmap使用原理
bitmap算法就是使用一個比特映射一個值,它可以用在整數排序和數據壓縮上,因為使用一個比特位去存儲一個數,所以它可以大大節省空間。
它的具體過程是:先根據數組中元素最大的數N計算需要分配多大的空間。
如果使用int型數組的形式來保存的話,一個int = 4字節 =4*8比特 = 32比特。也就是一個int數可以映射32個數據(圖1),然后需要找到最大的數Max,表示最多需要的位數,所以需要開辟的數組空間為int a[1+Max/32]。
然后需要推導一個整數a內如何映射32個數據,方法是將待存儲的數據模32,然后將a中相應位置的比特置為1。
依此方法映射每一個元素,待讀取的時候掃描每個比特位,遇到值為1的就還原該數字。
移位計算公式:
N/32就是將N的二進制右移log32(也就是5)位 : N>>5
N%32就是求N的后5位:N& 0x1F (0x1F = 00011111)
模32然后相應位置置為1: a[i] |= 1<< N & 0x1F
所以總的公式為: a[ N>>5 ] |= 1<< N & 0x1F
BitMap算法評價
- 優點:
- 運算效率高,不進行比較和移位;
- 占用內存少,比如最大的數MAX=10000000;只需占用內存為MAX/8=1250000Byte=1.25M。
- 缺點:
- 所有的數據不能重復,即不可對重復的數據進行排序。(少量重復數據查找還是可以的,用2-bitmap)。
- 所需要的空間隨著最大元素的增大而增大,當數據類似(1,1000,10萬)只有3個數據的時候,用bitmap時間復雜度和空間復雜度相當大,只有當數據比較密集時才有優勢。
(26) 布隆過濾器原理與優點
布隆過濾器是一個比特向量或者比特數組,它本質上是一種概率型數據結構,用來查找一個元素是否在集合中,支持高效插入和查詢某條記錄。常作為針對超大數據量下高效查找數據的一種方法。
它的具體工作過程是這樣子的:
假設布隆過濾器的大小為m(比特向量的長度為m),有k個哈希函數,它對每個數據用這k個哈希函數計算哈希,得到k個哈希值,然后將向量中相應的位設為1。在查詢某個數據是否存在的時候,對這個數據用k個哈希函數得到k個哈希值,再在比特向量中相應的位查找是否為1,如果某一個相應的位不為1,那這個數據就肯定不存在。但是如果全找到了,則這個數據有可能存在。
為什么說有可能存在呢?
因為不同的數據經過哈希后可能有相同的哈希值,在比特向量上某個位置查找到1也可能是由于某個另外的數據映射得到的。
支持刪除操作嗎
目前布隆過濾器只支持插入和查找操作,不支持刪除操作,如果要支持刪除,就要另外使用一個計數變量,每次將相應的位置為1則計數加一,刪除則減一。
布隆過濾器中哈希函數的個數需要選擇。如果太多則很快所有位都置為1,如果太少會容易誤報。
布隆過濾器的大小以及哈希函數的個數怎么選擇?
k 為哈希函數個數,m 為布隆過濾器長度,n 為插入的元素個數,p 為誤報率
(27) 布隆過濾器處理大規模問題時的持久化,包括內存大小受限、磁盤換入換出問題
(28)實現一個隊列,并且使它支持多線程,隊列有什么應用場景(阿里三面)
//評測題目: class FIFOQueue { vector<int> vec(initCap,0); int start=0,end=0; condition_variable cv; mutex m; bool flag=false;// isFullbool enqueue(int v) {unique_lock<mutex></mutex> lk(m);while(flag==true) cv.wait(lk);end=(end+1)%initCap;vec[end]=v;cv.notifyall();return true;}}int dequeue() {unique_lock<mutex></mutex> lk(m);if(start!=end){int val = vec[start];start=(start+1)%initCap;flag=false;cv.notifyall();return val;}else{flag=false;cv.notifyall();return -1;}} }以上代碼是面試時寫的,并沒有運行,也許有錯誤,請客觀參考
7. 智力題
(1) 100層樓,只有2個雞蛋,想要判斷出那一層剛好讓雞蛋碎掉,給出策略(滴滴筆試中兩個鐵球跟這個是一類題)
- (給定了樓層數和雞蛋數的情況)二分法+線性查找 從100/2=50樓扔起,如果破了就用另一個從0扔起直到破。如果沒破就從50/2=25樓扔起,重復。
- 動態規劃
(2) 毒藥問題,1000瓶水,其中有一瓶可以無限稀釋的毒藥,要快速找出哪一瓶有毒,需要幾只小白鼠
用二進制的思路解決問題。2的十次方是1024,使用十只小鼠喝一次即可。方法是先將每瓶水編號,同時10個小鼠分別表示二進制中的一個位。將每瓶水混合到水瓶編號中二進制為1的小鼠對應的水中。喝完后統計,將死亡小鼠對應的位置為1,沒死的置為0,根據死亡小鼠的編號確定有毒的是哪瓶水,如0000001010表示10號水有毒。
(3)
(4) 先手必勝策略問題:100本書,每次能夠拿1-5本,怎么拿能保證最后一次是你拿
尋找每個回合固定的拿取模式。最后一次是我拿,那么上個回合最少剩下6本。那么只要保持每個回合結束后都剩下6的倍數,并且在這個回合中我拿的和對方拿的加起來為6(這樣這個回合結束后剩下的還是6的倍數),就必勝。關鍵是第一次我必須先手拿(100%6=4)本(這不算在第一回合里面)。
(5) 放n只螞蟻在一條樹枝上,螞蟻與螞蟻之間碰到就各自往反方向走,問總距離或者時間。
碰到就當沒發生,繼續走,相當于碰到的兩個螞蟻交換了一下身體。其實就是每個螞蟻從當前位置一直走直到停止的總距離或者時間。
(6) 瓶子換飲料問題:1000瓶飲料,3個空瓶子能夠換1瓶飲料,問最多能喝幾瓶
拿走3瓶,換回1瓶,相當于減少2瓶。但是最后剩下4瓶的時候例外,這時只能換1瓶。所以我們計算1000減2能減多少次,直到剩下4.(1000-4=996,996/2=498)所以1000減2能減498次直到剩下4瓶,最后剩下的4瓶還可以換一瓶,所以總共是1000+498+1=1499瓶。
(7)在24小時里面時針分針秒針可以重合幾次
24小時中時針走2圈,而分針走24圈,時針和分針重合24-2=22次,而只要時針和分針重合,秒針一定有機會重合,所以總共重合22次
(8) 有一個天平,九個砝碼,一個輕一些,用天平至少幾次能找到輕的?
至少2次:第一次,一邊3個,哪邊輕就在哪邊,一樣重就是剩余的3個;
第二次,一邊1個,哪邊輕就是哪個,一樣重就是剩余的那個;
(9) 有十組砝碼每組十個,每個砝碼重10g,其中一組每個只有9g,有能顯示克數的秤最少幾次能找到輕的那一組砝碼?
砝碼分組1~10,第一組拿一個,第二組拿兩個以此類推。。第十組拿十個放到秤上稱出克數x,則y = 550 - x,第y組就是輕的那組
(10)生成隨機數問題:給定生成1到5的隨機數Rand5(),如何得到生成1到7的隨機數函數Rand7()?
思路:由大的生成小的容易,比如由Rand7()生成Rand5(),所以我們先構造一個大于7的隨機數生成函數。
記住下面這個式子:
比如 Rand25= 5( Rand5()-1 ) + Rand5()可以生成1到25之間的隨機數。我們可以只要1到21(3*7)之間的數字,所以可以這么寫
int rand7(){int x=INT_MAX;while(x>21){x=5*(rand5()-1)+rand5();}return x%7+1; }賽馬:有25匹馬,每場比賽只能賽5匹,至少要賽多少場才能找到最快的3匹馬?
- 第一次,分成5個賽道ABCDE,每個賽道5匹馬,每個賽道比賽一場,每個賽道的第12345名記為 A1,A2,A3,A4,A5 B1,B2,B3,B4,B5等等,這一步要賽5場。
- 第二次,我們將每個賽道的前三名,共15匹。分成三組,然后每組進行比賽。這一步要賽3場。
- 第三次,我們取每組的前三名。共9匹,第一名賽道的馬編號為1a,1b,1c,第二名賽道的馬編號為2a,2b,2c,第三名賽道的馬編號為3a,3b,3c。這時進行分析,1a表示第一名里面的第一名,絕對是所有馬中的第一,所以不用再比了。2c表示第二名的三匹里頭的最后一匹,3b和3c表示第三名里面的倒數兩匹,不可能是所有馬里面的前三名,所以也直接排除,剩下1b,1c,2a,2b,3a,共5匹,再賽跑一次取第一第二名,加上剛篩選出來的1a就是所有馬里面的最快3匹了。這一步要賽1場。
- 所以一共是5+3+1=9場。
燒 香/繩子/其他 確定時間問題:有兩根不均勻的香,燃燒完都需要一個小時,問怎么確定15分鐘的時長?
(說了求15分鐘,沒說開始的15分鐘還是結束的15分鐘,這里是可以求最后的15分鐘)點燃一根A,同時點燃另一根B的兩端,當另一根B燒完的時候就是半小時,這是再將A的另一端也點燃,從這時到A燃燒完就正好15分鐘。
掰巧克力問題 NM塊巧克力,每次掰一塊的一行或一列,掰成11的巧克力需要多少次?(1000個人參加辯論賽,1V1,輸了就退出,需要安排多少場比賽)
每次拿起一塊巧克力,掰一下(無論橫著還是豎著)都會變成兩塊,因為所有的巧克力共有N*M塊,所以要掰N*M-1次,-1是因為最開始的一塊是不用算進去的。
每一場辯論賽參加兩個人,消失一個人,所以可以看作是每一場辯論賽減少一個人,直到最后剩下1個人,所以是1000-1=999場。
8. 測試
1. App測試和Web測試的區別
-
web和app的區別
web 項目 ,一般都是b/s架構,基于瀏覽器的。
App則是C/S的,必須要有 客戶端 。那么在系統測試測試的時候就會產生區別了。
首先從系統架構來看的話,Web測試只要更新了服務器端, 客戶端 就會同步會更新。而且 客戶端 是可以保證每一個用戶的 客戶端 完全一致的。但是App端是不能夠保證完全一致的,除非用戶更新 客戶端 。如果是App下修改了服務端,意味著 客戶端 用戶所使用的核心版本都需要進行回歸測試一遍。 -
性能方面
web頁面可能只會關注響應時間。
App則還需要關心流量、電量、CPU、GPU、Memory這些了。 -
兼容方面
Web是基于瀏覽器的,所以更傾向于瀏覽器和電腦硬件,電腦系統的方向的兼容,不過一般還是以瀏覽器的為主。而瀏覽器的兼容則是一般是選擇不同的瀏覽器內核進行測試(IE、chrome、Firefox)。
App的測試則必須依賴phone或者是pad,不僅要看分辨率,屏幕尺寸,還要看設備系統。系統總的來說也就分為Android和iOS,不過國內的Android的定制系統太多,也是比較容易出現問題的。 -
相比較web測試,app更是多了一些專項測試:
一些異常場景的考慮以及弱網絡測試。這里的異常場景就是中斷,來電,短信,關機,重啟等。
而弱網測試是App測試中必須執行的一項測試。
包含弱網和網絡切換測試。需要測試弱網所造成的用戶體驗,重點要考慮回退和刷新是否會造成二次提交。需要測試丟包,延時的處理機制。避免用戶的流失。安裝、卸載、更新:
web測試是基于瀏覽器的所以不必考慮這些。而app是 客戶端
的,則必須測試安裝、更新、卸載。除了常規的安裝、更新、卸載還要考慮到異常場景。包括安裝時的中斷、弱網、安裝后刪除安裝文件,更新的強制更新與非強制更新、增量包更新、斷點續傳、弱網,卸載后刪除App相關的文件等等。界面操作
現在app產品的用戶都是使用的觸摸屏手機,所以測試的時候還要注意手勢,橫豎屏切換,多點觸控,事件觸發區域等測試。
2. 設計用例的方法、依據有那些
- 白盒測試
白盒測試用例設計有如下方法:語句覆蓋、判定覆蓋、條件覆蓋、判定/條件覆蓋、條件組合覆蓋和路徑覆蓋。依據就是代碼結構。
- 黑盒測試
黑盒測試用例設計方法:基于用戶需求的測試、等價類劃分方法、邊界值分析方法、錯誤推測方法、因果圖方法、判定表驅動分析方法、正交實驗法、場景法。依據是用戶需求規格說明書,詳細設計說明書。
3. 軟件測試的流程
-
測試需求分析階段:閱讀需求,理解需求,主要就是對業務的學習,分析需求點,參與需求評審會議
-
測試計劃階段:主要任務就是編寫測試計劃,參考軟件需求規格說明書, 項目總體計劃,內容包括測試范圍(來自需求文檔),進度安排,人力物力的分配,整體測試策略的制定。風險評估與規避措施有一個制定。
-
測試設計階段:主要是編寫測試用例,會參考需求文檔(原型圖),概要設計,詳細設計等文檔,用例編寫完成之后會進行評審。
-
測試執行階段:搭建環境,執行冒煙測試(預測試)-然后進入正式測試,bug管理直到測試結束 測試評估階段:出測試報告,確認是否可以上線
-
測試流程:了解用戶需求–>參考需求規格說明書–>測試計劃(人力物力時間進度的安排)–>編寫測試用例–>評審用例–>搭建環境–>測試包安排預測(冒煙測試)-正式測試-bug-測試結束出報告–>版本上線–>面向用戶
4. Android中造成APP閃退的原因總結
- 弱網絡情況下,服務端響應不及時,可能倒是閃退。(網絡異常引起的)
- 應用版本太低,會導致不兼容,造成閃退。(有些API在老版本中有,在新版本中沒有,造成對象為空引起閃退)
- APP的SDK和手機的系統不兼容。
- 緩存垃圾過多:由于安卓系統的特性,如果長時間不清理垃圾文件。會導致越來越卡,也會出現閃退情況。
- 設計不合理,1個接口,拉取的數據量太大,請求結果會很慢,且占用大量內存,APP會閃退(比如,我們現在做的記錄儀,進入相冊列表時候,要拉取所有圖片,拉取太慢了,就閃退了)
- 不同APP間切換,交互測試,可能會出現閃退。
- 權限問題。
5. 網頁很卡的原因
- 帶寬不足、硬件配置低、CPU或者是內存被占滿。
- http請求次數太多。
- 接收數據時間過長,如下載資源過大。
- JS腳本過大,阻塞了頁面的加載。
- 網頁資源過多、接受數據時間長、加載某個資源慢。
- DNS解析速度。
6. 單元測試、集成測試、系統測試 區別
- 粒度不同:
單元測試粒度最小,集成測試粒度居中,系統測試粒度最大。 - 測試方式不同:
單元測試一般由開發小組采用白盒方式來測試,集成測試一般由開發小組采用白盒加黑盒的方式來測試,系統測試一般由獨立測試小組采用黑盒方式來測試。 - 測試內容不同:
單元測試主要測試單元是否符合“設計”,集成測試既驗證“設計”,又驗證“需求”,系統測試主要測試系統是否符合“需求規格說明書”。 - 使用階段不同:
單元測試為開發人員在開發階段要做的事情,集成測試和系統測試為測試人員在測試周期內級層做的工作。
7. 如何對登錄界面進行測試
- 黑盒測試方法(功能測試)
輸入正確用戶名和密碼,驗證是否登陸成功
輸入正確的用戶名和錯誤的密碼,驗證是否登陸失敗并且提示信息正確
輸入未注冊的用戶名和任意的密碼,驗證是否登陸失敗并且提示信息正確
用戶名和密碼都為空,驗證是否登陸失敗并且提示信息正確
用戶名和密碼兩者之一為空
若啟用了驗證碼,輸入正確的用戶名密碼驗證碼是否能登陸成功
輸入正確用戶名和密碼,錯誤的驗證碼,能否登陸成功并且提示信息正確
用戶名和密碼是否大小寫敏感
頁面上的密碼框是否加密顯示
后臺系統第一次創建的用戶重新登錄時是否提示修改密碼
忘記用戶名和忘記密碼的功能是否可用
前段功能是否根據要求限制用戶名和密碼的長度
點擊驗證碼圖片是否可以更換驗證碼,更換后的驗證碼是否可用
刷新頁面是否會刷新驗證碼
如果驗證碼具有時效性,分別驗證時效內和時效外驗證碼的有效性
用戶登錄成功但是會話超時后是否重定向到用戶登錄界面
不同級別的用戶登錄系統后的權限是否正確
頁面默認定位焦點是否定位到用戶名輸入框中
快捷鍵tab和回車鍵是否可以正常使用
非功能性需求,從安全,性能,兼容三個方面
- 安全:
用戶密碼后臺存儲是否加密
用戶密碼在網絡傳輸過程中是否加密
密碼是否具有有效期,密碼有效期到期后是否提示修改密碼
不登陸的時候直接在瀏覽框中輸入登錄界面后的url地址,是否會重新定位到登陸界面
密碼輸入框是否不支持復制粘貼
頁面密碼輸入框中輸入的密碼是否可以在頁面源碼模式下被查看
用戶名和密碼輸入框中輸入xss跨站腳本攻擊字符串驗證系統的行為是否被篡改
連續多次登陸失敗后系統是否會阻止用戶后續的嘗試
統一用戶在同一終端的多種不同瀏覽器上登陸,驗證登錄功能的互斥性是否符合設計預期
同一用戶先后在不同終端的瀏覽器上登陸用戶名和密碼輸入框中輸入典型的sql注入攻擊字符串驗證系統的返回頁面
,驗證登陸是否有互斥性
- 性能測試:
單用戶登陸的響應界面是否符合預期
單用戶登陸時后臺請求數量是否過多
高并發場景下用戶登錄的響應界面是否符合預期
高并發場景下服務端的監控指標是否符合預期
高集合點并發場景下是否存在資源死鎖和不合理的資源等待
長時間大量用戶連續登錄和登出,服務器端是否存在內存泄漏
- 兼容性測試:
不同瀏覽器下驗證登陸功能的頁面顯示和功能正確性
相同瀏覽器的不同版本下驗證登陸功能的頁面顯示和功能正確性
不同終端的不同瀏覽器下驗證登陸功能的頁面顯示和功能正確性
不同分辨率下……
- 弱網測試
網絡切換和網絡延遲時登陸界面是否正常
是否支持第三方登陸
是否可記住密碼,記住的密碼是否加密
8. 微信紅包功能測試
- 在紅包錢數,和紅包個數的輸入框中只能輸入數字
- 紅包里最多和最少可以輸入的錢數 200 0.01
- 拼手氣紅包最多可以發多少個紅包 100、超過最大拼手氣紅包的個數是否有提醒
- 當紅包錢數超過最大范圍是不是有對應的提示
- 當發送的紅包個數超過最大范圍是不是有提示
- 當余額不足時,紅包發送失敗
- 在紅包描述里是否可以輸入漢字,英文,符號,表情,純數字,漢字英語符號,是否可以輸入它們的混合搭配
- 輸入紅包錢數是不是只能輸入數字
- 紅包描述里許多能有多少個字符 10個
- 紅包描述,金額,紅包個數框里是否支持復制粘貼操作
- 紅包描述里的表情可以刪除
- 發送的紅包別人是否可以領取、發的紅包自己可不可以領取 2人
- 24小時內沒有領取的紅包是否可以退回到原來的賬戶、超過24小時沒有領取的紅包,是否還可以領取
- 用戶是否可以多次搶一個紅包
- 發紅包的人是否還可以搶紅包 多人
- 紅包的金額里的小數位數是否有限制
- 可以按返回鍵,取消發紅包
- 斷網時,無法搶紅包
- 可不可以自己選擇支付方式
- 余額不足時,會不會自動匹配支付方式
- 在發紅包界面能否看到以前的收發紅包的記錄
- 紅包記錄里的信息與實際收發紅包記錄是否匹配
- 支付時可以密碼支付也可以指紋支付
- 如果直接輸入小數點,那么小數點之前應該有個0
- 支付成功后,退回聊天界面
- 發紅包金額和收到的紅包金額應該匹配
- 是否可以連續多次發紅包
- 輸入錢數為0,"塞錢進紅包"置灰
- 弱網時搶紅包,發紅包時間
- 不同網速時搶紅包,發紅包的時間
- 發紅包和收紅包成功后的跳轉時間
- 收發紅包的耗電量
- 退款到賬的時間
- 蘋果,安卓是否都可以發送紅包
- 電腦端可以搶微信紅包
- 發紅包界面沒有錯別字
- 搶完紅包界面沒有錯別字
- 發紅包和收紅包界面排版合理,
- 發紅包和收到紅包界面顏色搭配合理
- 對方微信號異地登錄,是否會有提醒 2人
- 紅包被領取以后,發送紅包人的金額會減少,收紅包金額會增加
- 發送紅包失敗,余額和銀行卡里的錢數不會少
- 紅包發送成功,是否會收到微信支付的通知
- 紅包描述,可以通過語音輸入
- 可以指紋支付也可以密碼支付
9. HR面
1. 自我介紹
(HR面試的自我介紹可以側重軟實力部分,項目技術方面介紹可以適當少一些)
2. 項目中遇到的最大難點
-
在項目中曾經遇到了新的框架不知道該如何上手的問題,以及面對新的概念,新的技術不知道從何學起。解決的辦法是在官網尋找說明文檔和demo,按照說明文檔上的內容一步步了解,以及咨詢身邊有用過這個框架的同學,或者在CSDN上尋找相關博客。
-
項目的時間比較緊迫,沒有那么多的時間可以用。解決方法是把還沒有完成的項目分一個輕重緩急,在有限的時間里,先做重要而且緊急的,然后完成緊急的,再做重要的。利用輕重緩急做一個取舍。
3. 項目中的收獲
一個是了解了相關框架的使用方法(比如Dataframe的使用,xgboost的使用等等),這些框架或者技術可以在以后的開發中使用到。和對自己開發能力的鍛煉。
一個是鍛煉了與他人的交流能力,因為在團隊項目里經常會跟別人匯報自己的想法和進度,同時也會跟其他成員溝通模塊之間的交互,所以在這個過程中對自己的表達能力和理解能力都是一個很大的提升。
4. 可以實習的時間,實習時長
一定要往長了說!半年起步,最好七八個月,因為實習生是可以隨時跑路的。而且實習時間越長HR越青睞。
5. 哪里人
6. 說一下自己的性格
我是比較內向謹慎的人,平時做的多說的少。比較善于總結,在與人交流的時候更傾向于傾聽別人的意見后才發言。并且別人都說我辦事認真靠譜。
7. 你的優缺點是什么
我的缺點是容易在一些細節的地方花費太多的時間,有時候過分追求細節。并且我的實習經驗比較缺乏,對于實際項目的業務流程和工作流程不是很了解。(所以我打算通過實習來熟悉實際的軟件開發的流程和技術。)
我的優點是責任心比較強,做事比較負責,在校期間我負責的大創項目進展很順利,我經常組織組員們進行討論和推進項目的開發,最后這個項目得到了92的評分,在同級別里面是比較高的。
8. 有什么興趣愛好,畫的怎么樣/球打的如何/游戲打的怎么樣
平時的愛好是畫畫打游戲,在CSDN寫寫博客,還有就是看書,我很喜歡學到新知識掌握新技能的感覺。
9. 看過最好的一本書是什么
技術類:編程之美 機器學習西瓜書 STL源碼剖析 劍指offer C++primer plus
非技術類:明朝那些事兒 香水(聚斯金德) 解憂雜貨店 人類簡史 沉默的大多數 與時間做朋友(李笑來) 千年歷史千年詩
10. 學習技術中有什么難點
11. 怎么看待加班
我覺得 任何一家單位都有可能要加班。如果自己的工作沒有按時完成,那自覺加班是理所當然的,當然,自己要不斷提高工作效率,避免這種原因導致的加班。如果遇到緊急任務或者突發狀況時,為了順利配合團隊完成任務,我會盡自己所能加班共同完成。
12. 覺得深圳怎么樣(或者其他地點)
13. 遇見過最大的挫折是什么,怎么解決的
14. 職業規劃
在工作的第一個階段,先盡快適應工作的環境,包括開發環境開發工具和工作流程等,把自己負責的部分快速的完成,不能出差錯。第二個階段要熟悉整個項目的業務流程,所有模塊的結構和依賴關系,知道每個模塊為什么要這么設計,以及它們的實現細節。第三個階段要培養獨立設計一個項目的能力,可以獨立或者在別人的協作下設計項目的模塊分工和架構。
在工作和項目中多寫博客或者筆記,積累技術影響力,將經驗總結成文檔。同時與同事搞好關系,嘗試培養領導能力和組織能力。
15. 目前的offer情況
可以如實說
16. 你最大的優勢和劣勢是什么
- 優勢:做事情有主動性,不拖沓,有責任心。舉個例子:在做論文課題的時候,幾乎都是我自己找老師匯報進度和找老師討論問題,很少有被老師催的時候。每一次跟老師討論之后都會將討論的內容和老師提出的意見進行詳細記錄。在中軟杯的比賽中,主動承擔答辯ppt的制作,并且每次排練之后都迅速對ppt的修改意見進行落實修改,前前后后改了十幾版。
- 劣勢:有時候做事情比較急躁,容易導致粗心。
17. 介紹在項目里面充當的角色
18. 介紹一下本科獲得的全國賽獎項的情況
19. 最有成就感的事情/最驕傲的一件事情
- 本科的時候跟優秀的隊友們一起參加中國軟件杯比賽努力了四個月,最后獲得了該賽題的第一名和全國一等獎的好成績
- 保研夏令營拿到了四個學校的offer
20. 在實驗室中擔任什么角色,參加的XXX能聊聊嗎
22. 用兩個詞來形容自己
踏實 細心
23. 反問
技術面反問:
- 崗位主要負責的工作
- 從規劃到完成任務的工作流程是什么?
- 最常用的技術
- 還需要學習哪些內容
- 最近半年工作壓力最大的時候,面臨什么情況?
- 平時團隊內是如何溝通交流的和分配任務的?
- 不能在預期時間內完成工作會怎樣?
- 團隊正在經歷的尚未解決的挑戰是什么?
HR面反問:
- 部門的組織架構是怎么樣的
- 崗位的考核標準
- 試用期的情況
- 針對新員工有哪些培訓
- 五險一金繳納情況
- 年假、事假、病假、產假等每年都有多少天?
- 公司的晉升通道和晉升機制是怎樣的?
總結
以上是生活随笔為你收集整理的2022秋招面经(C++软开)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java学习笔记8-1——汇编语言入门
- 下一篇: php显示错误