【C++後臺開發面經】面試總結第三波:針對後臺開發相關基礎知識分類總結

前言

    面試總結第三波,關於後臺開發面試相關基礎知識,數據結構、算法、linux操作系統、計算機網絡、C++、數據庫進行分類總結。

後端面試總結

目錄

後端面試總結

1、數據結構

鏈表和數組的區別

樹的先序、中序、後序遍歷

雙鏈表的增刪查

排序

2、算法

3、linux

指令

共享內存

ELF文件(可執行可鏈接文件)

進程間通信

makefile文件編寫

gdb調試

如何定位內存泄漏?

動態鏈接和靜態鏈接的區別

32位系統一個進程最多多少堆內存

進程/線程/協程

多進程和多線程的區別

寫一個c程序辨別系統是64位還是32位

寫一個c程序辨別系統是大端還是小端節序

常見信號

同步機制,什麼是死鎖,如何避免死鎖

異步機制

epoll水平觸發(LT)和邊沿觸發(ET)

exit()和_exit()的區別

如何實現守護進程

內存管理機制

任務調度機制

系統調用和標準庫函數

系統如何將一個信號通知到進程

linux中/etc和/var目錄

線程的五大狀態

4、計算機網絡

tcp和udp區別

tcp頭

connect

 如果select返回可讀,結果只讀到0字節,什麼情況?

tcp選項

socket的讀和寫

 TCP三次握手/4次揮手

從輸入URL到顯示頁面,後臺發生了什麼?

DNS解析過程

 ARP地址解析協議

擁塞控制

ICMP協議

cookie和session

http協議

驚羣問題

TCP的RST報文

MSL、TTL和RTT

5、C++

new與malloc的區別

使用引用減少拷貝構造函數使用次數

sizeof

虛繼承

模板特例化與實例化

棧溢出幾種情況

模板與多態的使用場景

 STL容器的線程安全

C/C++中volatile關鍵字

STL sort函數實現詳解

6、數據庫

基本操作

一條SQL語句執行得很慢的原因有哪些?

B+樹索引和hash索引的區別

聚集索引和非聚集索引

 悲觀鎖與樂觀鎖

MySql主從複製原理

使用explain優化sql

SQL連接(內連接、外連接、交叉連接)

char/varchar/nvarchar


1、數據結構

鏈表和數組的區別

內存分佈:鏈表是無連續的內存空間,通過指針來實現鏈式存儲;數組是一段連續的內存空間,一般大小需提前知道。

增:鏈表:無序鏈表:O(1),有序鏈表:O(n);數組:無序數組:內存充足O(1),內存不充足O(O(n)+1)=O(n),有序數組:O(n) 查找+移動

刪:鏈表:O(O(n)+1)=O(n) 找到+刪除;數組:末尾 O(1),非末尾O(1)+O(n) =O(n) 刪除+移動

查:鏈表:O(n);數組:O(1)

樹的先序、中序、後序遍歷

波蘭序列:先序遍歷

逆波蘭序列:後序遍歷

表達式:8+(3-1)*5

先序遍歷:+ 8 * - 3 1 5

中序遍歷:8 + 3 - 1 * 5

後序遍歷:8 3 1 - 5 * +

棧:也可以模擬整個過程

雙鏈表的增刪查

結構圖:

插入:

刪除:

深度優先搜索

廣度優先搜索

平衡二叉樹:操作時間複雜度logn,維持平衡比較複雜

二叉排序樹:中序遍歷爲有序序列

紅黑樹:自平衡二叉查找樹,從根節點到葉子節點最長長度不多於最短長度的兩倍

B樹:多路搜索樹

B+樹:特殊的B樹,數據都在葉子節點,根節點一般用於索引,常用於文件的索引結構

排序

堆排:O(nlogn)、不穩定、n大時較好

希爾:O(nlogn)、不穩定、n小時較好

快排:平均O(nlogn)、不穩定、n大時較好

冒泡:O(n^{2})、穩定、n小時較好

穩定排序:冒泡、插入、歸併

不穩定排序:快排、希爾排序、選擇排序、堆排序

2、算法

典型的算法案例

  • 存儲10億個INT型qq號,但所給的內存只有1G:一個int佔4個字節,所以一共需要4G內存,使用哈希+位圖,每個qq用一位進行存儲,一個int可以存儲32個了。
  • 在1億數中找出前1000個大的數,內存只有4KB:堆排序
  • 找出第K大的數:快排
int find_k(vector<int> &q,int k,int left,int right){
    int l=left-1,r=right+1,x=q[l+r>>1];
    while(l<r){
        do l++; while(q[l]<x);
        do r--; while(q[r]>x);
        if(l<r) swap(q[l],q[r]);
        else {
            if(r==k-1) return q[r];
            else if(r>k-1) return find_k(q,k,left,r-1);
            else return find_k(q,k,r+1,right);
        }
    }
}
  • 大整數乘法:利用分治思想

3、linux

指令

netstat:顯示與IP、TCP、UDP、ICMP協議相關的統計數據,一般用於檢驗本機各端口的網絡連接情況

tcpdump:對網絡上的數據包進行捕獲,輸出信息:系統時間 來源主機.端口>目的主機.端口 數據包參數

ipcs:提供關於一些進程間通信方式的信息,包括共享內存、消息隊列、信號。

ipcrm:移除一個消息對象,共享內存、信號集,同時將與ipc對象相關鏈的數據也一起移除。

top:相當於windows下的資源管理器,能夠動態實時的顯示系統中進程的資源佔用情況。包括進程信息、CPU信息、內存信息。top -Hp pid查看某個進程的線程信息。

awk:強大的文本分析工具,對數據分析並生成報告,使用方法:awk '{pattern+action}'{filenames} 。

sed:實現對文件的增刪查改,sed [ ] '{  }' {filename}  p 、d、 =(打印匹配行的行號)、-n(只要需要的輸出)、-e(允許多項編輯)、-i(修改文件內容)

ps aux --sort -rss:對進程按照內存使用情況進行排序

ps aux --sort=-pcpu | head -10:按CPU使用比排序

共享內存

實現原理:通過將不同的虛擬內存映射到同一塊物理內存,實現共享;

共享內存段被映射到進程空間後,存在於進程空間的什麼位置:棧和堆之間的共享段

共享內存段最大限制是:32M

ELF文件(可執行可鏈接文件)

源代碼經過編譯器編譯後生成的文件叫做目標文件,而目標文件經過編譯器鏈接後得到的就是可執行文件。動態鏈接庫.so和靜態鏈接庫.a也是以可執行文件格式存儲。

基本結構:ELF Header、程序頭、節點表、ELF Sections

注:段與節的區別,段是程序執行的必要組成,當多個目標文件鏈接成一個可執行文件時,會將相同權限的節合併到一個段中。相比而言,節的粒度更小。

.bss節:存在於data段中,佔用空間不超過4字節,僅表示這個節本身的空間,由於.bss節未保存實際的數據,所以節類型爲SHT_NOBITS。

分類:1)可重定向文件:保存着代碼和適當的數據,例如:目標文件或靜態庫文件,即後綴爲.o和.a的文件

           2)可執行文件:保存着用來執行的程序,例如:bash,gcc等

           3)共享目標文件:共享庫,用來連接編輯器和動態鏈接器鏈接,例如:後綴爲.so的文件

進程間通信

管道:pipe(無名管道)有血緣關係(父子進程)、fifo(有名管道)無血緣關係

消息隊列:內核中有一個消息隊列,可以向隊列添加消息,也可以向隊列獲取消息,消息的鏈表,存放在內核中並由消息隊列標識符表示。消息隊列提供了一個從一個進程向另一個進程發送數據塊的方法,每個數據塊都可以被認爲是有一個類型,接收者接收的數據塊可以有不同的類型。每個消息的最大長度是有上限的。內核爲每個IPC對象維護了一個數據結構struct ipc_perm,用於標識消息隊列,讓進程知道當前操作的是哪個消息隊列。msgget、msgctl、msgsnd、msgrcv

信號量:ctrl C,kill

內存映射:mmap,最快

socket:用於兩個不同的主機間

makefile文件編寫

SrcFiles=$(wildcard *.c)
ObjFiles=$(patsubst %.c,%,$(SrcFiles))

all:$(ObjFiles)

%:%.c
    gcc -o $@ $^

.PHONY:clean

clean:
    -rm -f $(ObjFiles)

gdb調試

編寫makefile文件時,對於gcc後面加上-g

多線程調試:1)先在進入子線程前,加上sleep,防止attach後,不知道運行到哪裏了,確保在子進程在睡眠狀態結束之前,attach上子進程;2)先ps -aux|grep xxx,查出子進程id;3)attach id;4)stop、break、continue、step等

如何定位內存泄漏?

可用一個工具,VLD(Visual Leak Detector),可安裝作爲VS的一個插件。

動態鏈接和靜態鏈接的區別

靜態鏈接:#pragma comment(lib,“test.lib”),靜態鏈接的時候,載入代碼就會把程序會用到的動態代碼或動態代碼的地址確定下來;

動態鏈接:使用這種方法的程序並不在一開始就完成動態鏈接,而是直到真正調用動態庫代碼時,載入程序才計算(被調用的那部分)動態代碼的邏輯地址。所以這種方法使程序初始化時間較短,但運行期間的性能比不上靜態鏈接的程序。

動態庫與靜態庫

動態庫:多個程序可以使用同一個動態庫,啓動多個應用程序的時候,只需要將動態庫加載到內存一次即可

靜態庫:代碼的裝載速度快,執行速度也快,因爲編譯時只需要把那部分鏈接進來,應用程序相對較大,而且多個應用程序使用的話,會被裝載多次,浪費內存。

找不到頭文件:編譯時使用-I來指定頭文件路徑

找不到庫文件:把庫文件放入系統的庫文件目錄下,如/lib;或者把庫文件所在的目錄加入到對應的環境變量中

32位系統一個進程最多多少堆內存

理論上爲:4G-1G=3G,如果報錯,會出現std::bad_alloc

棧內存一般只有幾M

進程/線程/協程

進程:系統分配資源的基本單位,每一個進程創建出來,都會分配三種基本的內存資源,分別是代碼段、數據段和堆棧段。棧空間是子任務(線程、協程)獨立存放自己的數據地方,比如:函數調用、參數、返回值和局部變量。這樣一來,子任務之間就可以獨立運行,而且還可以共享堆空間中的變量數據。

線程:CPU調度的基本單位,操作系統不僅僅維持一個進程表,而且還會維持一個線程表,這樣操作系統就可以把線程作爲調度單位。線程是在進程內創建,可以共享進程的資源,所以,線程自身獨立的資源依賴就會少很多,因爲只需要爲每個線程分配獨立的棧空間。而線程的棧空間是固定大小的,如果程序比較複雜,或者裏面的數據量大,爲了不出現“棧空間不足”的錯誤,就必須把棧空間設置的足夠大才行。

協程:是可以在應用態協作的程序,它的調度不是操作系統處理,而是應用系統自己來調度處理,也稱爲輕量級線程。協程作爲應用系統內調度的子任務單元,當然也是會共享進程的各種資源,除了自己的棧空間(函數調用、參數、返回值、局部變量)。

而協程和線程主要區別有兩個,1)最大的就是調度方式,線程是操作系統調度,協程是應用系統自己調度;2)協程的棧空間是可以動態調整的,這樣空間利用率就可以更高,一個任務需要2K空間就分配2K空間,一個任務需要20M空間就分配20M,而不用擔心棧空間不夠或者空間浪費。

協程的優勢:1)協程可以更好的利用CPU,不用把CPU浪費在線程調度和上下文切換上;2)協程可以可以更好的利用內存,不用全部分配一個偏大的空間,只需要分配需要的對應空間即可。

隊列:應用中維持數據的一個隊列,很多時候會是一個數組或者鏈表。隊列裏保存的也不是一個子任務,而只是一個數據,具體這個數據拿出來之後要啓動什麼子任務,這個隊列是不關心的。

隊列只是一個緩衝帶,把更多的獨立數據先臨時保持住,應用系統有多大的能力消化吸收就從裏面用多快的速度進行處理。

多線程模式是爲每個網絡請求創建一個線程來處理這個請求,當請求執行結束,再銷燬這個線程。於是,當網絡的請求量高的時候,意味着反覆的爲這些請求創建和銷燬線程,這個開銷就變得比較大,效率也就下降了。

在多進程模式下,進程是複用的,不會反覆的創建和銷燬,所以就沒有之前多線程模式那樣大的資源浪費了。當多進程內存開銷大,系統調度開銷大,所以也就意味着併發量相對就會較小。

線程池:可以複用線程,很好的避免了線程頻繁創建和銷燬所帶來的損耗。但線程棧空間是固定的,在一些個別請求中,數據量很大時,也可能會不得已要設置較大的棧空間,這樣一來,內存浪費也是比較嚴重了。

多進程和多線程的區別

多進程:使用fork,子進程會複製父進程的task_struct結構體,併爲子進程的堆棧分配物理頁。理論上,子進程應該完整地複製父進程的堆、棧以及數據空間,但實際上是進行寫時複製。

有各自的對立運行地址空間,互不干擾,可靠性高,進程的創建、銷燬、切換複雜,速度慢,佔用內存大,通信複雜,但同步簡單,適用於多核多機分佈,CPU密集型

多線程:線程就是把一個進程分爲很多片,每一個片都可以是一個獨立的流程,進程是一個拷貝的流程,而線程沒有拷貝這些額外的開銷。

共享同一個進程的地址空間,但線程間會相互影響,一個意外終止會導致同一個進程的其他線程也終止,可靠性弱,線程的創建、銷燬、切換簡單,速度快,通信簡單,同步複雜,適用於單機多核分佈式,I/O密集型

哪些東西是線程私有的?

1)線程id;2)堆棧;3)寄存器值,以便線程切換時得以恢復;4)錯誤返回碼error;5)信號屏蔽碼;6)線程的優先級

寫一個c程序辨別系統是64位還是32位

判斷一個指針的size,如果是4則是32位,如果是8則是64位

寫一個c程序辨別系統是大端還是小端節序

union,如果是小端:則從高字節向低字節讀,如果是大端:則從低字節向高字節讀

常見信號

SIGHUP 終端掛起,終止進程

SIGINT 鍵盤中斷 CTRL+C

SIGQUIT CTRL+D 終止進程

SIGKILL 強制退出

SIGALRM 定時器超時

同步機制,什麼是死鎖,如何避免死鎖

互斥鎖:如果要進入一段臨界區需要多個mutex鎖,那麼就很容易導致死鎖。解決:申請鎖的時候按照固定順序,或者及時釋放不需要的互斥鎖就可以。

讀寫鎖:讀時可進入臨界區

自旋鎖:適用於臨界區短,線程需要等待的時間也短,即便輪詢浪費CPU資源,也浪費不了多少,還省了context切換的開銷。

條件變量:在合適的時候喚醒正在等待的線程,必須和互斥鎖聯合起來用

信號量:wait和post

死鎖:兩個進程或兩個以上進程在執行過程中,因爭奪資源而造成的相互等待的現象。

死鎖發生四個條件:1)互斥條件;2)請求和保持條件——資源一次性分配;3)不可剝奪條件——當進程新的資源未得到滿足時,釋放已佔有的資源;4)環路等待條件——系統給每個資源分配一個序號,每個進程按編號遞增請求資源,釋放則相反。

異步機制

信號:進程也不知道信號到底什麼時候到達

epoll:一種高效處理IO的異步通信機制

epoll水平觸發(LT)和邊沿觸發(ET)

水平觸發:只要緩衝區有數據就會一直觸發

邊沿觸發:只有在緩衝區增加數據的那一刻纔會觸發

在設置邊沿觸發時,因爲每次發送消息只會觸發一次(不管緩衝區是否還留有數據),所以必須把數據一次性讀取出來,否則會影響下一次消息。需要用while(recv())這裏會發生阻塞,所以需要設置非阻塞狀態。

exit()和_exit()的區別

exit():要調用終止處理程序和清楚IO緩存後再退出

_exit():立即終止程序

如何實現守護進程

1)fork();2)父進程退出;3)子進程當會長setsid;4)切換當前目錄到home目錄;5)設置掩碼權限umask(0);6)關閉文件描述符;7)執行核心程序

內存管理機制

有物理內存和虛擬內存,採用分頁管理,每個頁的大小是4KB,進程運行時,先分配虛擬頁表,而不把數據和代碼加載到物理內存。

任務調度機制

經過兩個過程:選擇算法+上下文切換

任務調度可分爲:主動調度和被動調度(被強佔)

系統調用和標準庫函數

很多庫函數是對系統調用的一個封裝

系統調用:需要在用戶空間和內核空間之間切換,開銷較大

標準庫函數:可以利用緩衝區,等往緩衝區寫完了,再系統調用一次性把數據寫入到硬件媒介

系統如何將一個信號通知到進程

進程有一個鏈表的數據結構,維護一個未決信號鏈表,信號在進程中註冊,其實就是把該信號加入到這個未決信號鏈表中。進程處理信號的時機就是從內核態即將返回用戶態的時候。

執行用戶自定義的信號處理函數的方法很巧妙,把該函數的地址放在用戶棧棧頂,進程從內核態返回到用戶態的時候,先彈出信號處理函數地址,於是就去執行信號處理函數了,然後再彈出,纔是返回進入內核時的狀態。

被屏蔽的信號,取消屏蔽後還會被檢查

linux中/etc和/var目錄

/var:包含系統一般運行時要改變的數據,通常這些數據所在的目錄的大小是要經常變化或擴充的。

/var/lib:存放系統正常運行時要改變的文件。

/etc:包含各種系統配置文件。

線程的五大狀態

新建狀態——>就緒狀態——>運行狀態——>阻塞狀態、死亡狀態

阻塞狀態——>就緒狀態

4、計算機網絡

tcp和udp區別

tcp:面向連接的、可靠的、面向字節流、數據傳輸慢、僅有兩方進行彼此通信

udp:無連接、不可靠、面向報文、數據傳輸快、可以一對多通信,所以適用於廣播和組播

tcp頭

最長60字節=固定20字節+可變長的可選信息40字節

源端口、目的端口、32位序列號、32位確認號、6個標誌位(SYN、ACK、FIN)、16位滑動窗口大小

connect

connect在進行三次握手,如果失敗情況,需要等待75s的超時時間,即出現阻塞,設置套接字爲非阻塞狀態:setBlockOpt(fd,false);,然後使用select或者poll等機制來檢測套接字一定時間,如果在超時時間內不可寫,則認爲connect失敗。

 如果select返回可讀,結果只讀到0字節,什麼情況?

如果在一個描述符碰到了文件尾端,則select會認爲該描述符是可讀的。然後調用read,它返回0,這是指示到達文件尾端方法。

tcp選項

kind=2:最大報文段長度(MSS)選項

kind=3:窗口擴大因子選項

kind=4:選擇性確認(SACK)選項

kind=8:時間戳選項

socket的讀和寫

讀:1)接收緩衝區中已接收的數據字節數大於等於閾值時,這時不阻塞,返回大於0,可用SO_RCVLOWAT來設置此閾值

       2)接收到對方發過來的FIN的TCP連接時,這時不阻塞, 返回0(即文件結束符,FIN包體長度爲0字節)

       3)socket是一個用於監聽的socket,並且已經完成的連接數爲非0。這樣的socket處於可讀狀態,是因爲socket收到了對方的connect請求,執行了三次握手的第一步,對方發送SYN請求過來,使監聽socket處於可讀狀態。

       4)有一個socket有異常錯誤條件待處理,不會阻塞,返回一個錯誤-1

寫:1)socket發送緩衝區中可用空間字節數大於等於socket發送緩衝區閾值。

       2)連接的寫這一半關閉,對於這樣的socket的寫操作將會產生信號SIGPIPE

       3)有一個socket異常錯誤條件待處理

 TCP三次握手/4次揮手

三次握手:客戶端向服務器端發起連接請求,讓SYN標誌位置1,並填入32位的隨機序號,一起發送到服務器端;服務器端同樣讓SYN標誌位置1,並填入32位隨機序號,和讓ACK標誌位也置1,並把客戶端發送過來的隨機序號+1作爲確認序號,一起發送到客戶端;客戶端讓ACK標誌位置1,把服務端發送過來的隨機序號+1作爲確認序號,發送到服務端。

客戶端:connect()——>SYN_SENT——>ESTABLISHED

服務端:listen()——>SYN_RECVD——>ESTABLISHED

四次揮手:任意一端都可以發起斷開請求,假設客戶端向服務端發起斷開請求,客戶端讓FIN標誌位置1,把最後一次收到服務端發過來的確認號作爲序號,和讓ACK標誌位置1,把收到的數據作爲確認號,一起發送到服務端;服務端先發送ACK確認;再同樣發送FIN;客戶端發送ACK確認

客戶端:FIN_WAIT_1——>FIN_WAIT_2——>TIME_WAIT——>CLOSED

服務端:CLOSE_WAIT——>LAST_ACK——>CLOSED

TIME_WAIT:會等待2MSL(報文最大生存時間),等待服務端收到最後的ACK,對方如果沒收到,就會超時重傳FIN;可以使本次連接中遲緩的時間所產生的報文都從網絡中消失,這樣可以使下一個新的連接中不會出現這種舊的連接請求報文。

TIME_WAIT狀態如何避免

首先服務器可以設置SO_REUSEADDR套接字選項來通知內核,如果端口忙,但TCP連接位於TIME_WAIT狀態時可以重用端口。在一個非常有用的場景就是,如果你的服務器程序停止後想立即重啓,而新的套接字依舊希望使用同一端口,此時SO_REUSEADDR選項就可以避免TIME_WAIT狀態。

從輸入URL到顯示頁面,後臺發生了什麼?

1)在瀏覽器輸入網址

2)瀏覽器查詢域名的IP地址:DNS查找:1.瀏覽器緩存;2.系統緩存;3.路由器緩存;4.ISP DNS緩存;5.遞歸搜索

3)瀏覽器給web服務器發送一個HTTP協議

4)負載均衡:當一臺服務器無法支持大量的用戶訪問時,將用戶分攤到兩個或多個服務器上的方法叫負載均衡。Nginx是一款面向性能設計的HTTP服務器。web服務器受到請求,產生響應,並將網頁發送給Nginx負載均衡服務器。Nginx負載均衡服務器將網頁傳遞給filters鏈處理,之後發回給我們的瀏覽器。

4)服務器給瀏覽器發送HTML響應

5)瀏覽器開始顯示HTML

6)瀏覽器發送獲取嵌入在HTML中的對象,比如圖片 

DNS解析過程

1)用戶向ISP-DNS發起DNS遞歸查詢,必須返回域名-IP映射關係;2)ISP-DNS查本地cache;3)向根-DNS發起迭代DNS查詢;4)返回通用頂級域名;5)繼續迭代返回IP

 ARP地址解析協議

IP地址與物理地址之間的轉化。

獲取目的端口的MAC地址(在一個以太網中)步驟:1)發送ARP請求的以太網數據幀給以太網上的每個主機,即廣播(以太網源地址全填1)。ARP請求幀包含了目的主機的IP地址;2)目的主機收到了該ARP請求後,會發送一個ARP應答,裏面包含了目的主機的MAC地址。

擁塞控制

避免更多的數據包流入到網絡中,引起路由器和數據鏈路過載。

慢啓動、擁塞避免、快重傳、快速恢復

ssthresh(慢開始門限)、cwnd(擁塞窗口) 

ICMP協議

網絡層協議,可以傳遞差錯報文以及其他信息

ping程序:發送ICMP回顯請求給主機,等待主機返回回顯應答,來測試另一臺主機是否可達。可根據往返時間來確定主機離我們多遠,應答和請求之間進行通過序列號字段來實現,從0開始,每次加1,通過ping打印序列號來確定分組是否有丟失、失效或者重複。

ping獲取目的主機的地址:通過IP記錄路由選項,ping提供了一個-R選項記錄路由功能,最後複製到ICMP回顯應答中。 

cookie和session

http請求是無狀態的, 也就是說即使第一次和服務器連接後並且登陸成功後,第二次請求服務器依然不能知道當前請求是哪個用戶。第一次登陸後,服務器返回一些數據(cookie)給瀏覽器,然後瀏覽器保存在本地,當該用戶發送第二次請求時,就會自動把上次請求存儲的cookie發送給服務器。一般不會超過4KB。

session:存儲在服務器,更安全,不易竊取,會佔用服務器資源

cookie:存儲在本地瀏覽器

http協議

1)http協議與TCP/IP協議的關係

     http的長連接和短連接本質上是TCP長連接和短連接。http屬於應用層協議,在傳輸層使用tcp協議,在網絡層使用ip協議。ip協議主要解決網絡路由尋址問題,tcp協議主要解決如何在ip層之上可靠地傳遞數據包,使得網絡上接收端收到發送端發出的所有包,並且順序與發送順序一致。TCP協議是可靠的、面向連接的。

2)http協議是無狀態的

    協議對事務處理沒有記憶能力,服務器不知道客戶端是什麼狀態。也就是說,打開一個服務器上的網頁和上一次打開這個服務器上的網頁之間沒有任何聯繫。

3)什麼是長連接、短連接?

      http/1.0中默認使用短連接,也就是說,客戶端和服務器每次進行一次http操作,就建立一次連接,任務結束就中斷連接。當客戶端瀏覽器訪問的某個HTML或其他類型的web頁中包含有其他的web資源,每遇到這樣一個web資源,瀏覽器就會重新建立一個http會話。所以併發量大,但每個用戶無需頻繁操作情況下用短連接好。

     而從http/1.1起,默認使用長連接,用以保持連續性。使用長連接的http協議,會在響應頭加入這行代碼:Connection:keep-alive,在使用長連接的情況下,當一個網頁打開完成後,客戶端與服務器之間用於傳輸http數據的tcp連接不會關閉,客戶端再次訪問這個服務器時,會繼續使用這一條已經建立的連接。keep-alive不會永久保持連接,它有一個保持時間,可以在不同的服務器軟件中設定這個時間。實現長連接需要客戶端與服務端都支持長連接。多用於操作頻繁點對點的通訊,而且連接數不能太多情況。

驚羣問題

       驚羣是指當多個進程/線程在等待同一個資源時,每當資源可用時,所有的進程/線程都來競爭資源的現象,但最終只可能有一個進程/線程對該事件進行處理,其他進程/線程會在失敗後重新休眠,這種性能浪費就是驚羣。驚羣造成的結果是系統對用戶進程/線程頻繁的做無效的調度、上下文切換,系統性能大打折扣。

      在linux2.6版本以後,內核已經解決了accept()函數“驚羣”問題,大概處理的方式就是,當內核接收到一個客戶連接後,只會喚醒等待隊列上的第一個進程或線程。

      在實際中,大都使用select、poll或epoll機制,此時,服務器不是阻塞在accept,而是阻塞在select、poll或epoll_wait,這種情況下的“驚羣”仍然需要考慮。fork多個子進程,在有事件發生時調用epoll_wait開始accept連接,只有一個進程接收到連接,其他沒有,說明沒有發生驚羣現象,這是因爲只會喚醒等待隊列上的第一個進程或線程。但這個只是部分解決,比如:在epoll_wait後調用sleep一次,就會出現驚羣問題,解決:可使用mutex互斥鎖解決這個問題,每個子進程在epoll_wait之前先去申請鎖,申請到則繼續處理,獲取不到則等待,並設置了一個負載均衡的算法(當某一個子進程的任務量達到總設置量的7/8時,則不會再嘗試去申請鎖)來均衡各個進程的任務量。

TCP的RST報文

RST:用於復位因某種原因引起出現的錯誤連接,也用來拒絕非法數據和請求。如果接收到RST位時候,通常發生了某些錯誤。發送RST包關閉連接時,不必等緩衝區的包都發出去,直接就丟棄緩衝區中的包,發送RST;接收端收到RST包後,也不必發送ACK包來確認。Socket.close()表示我不在發送也不接受數據了。

產生RST報文的幾種場景

1)connect一個不存在的端口

2)向一個已經關掉的連接send數據

3)向一個已經崩潰的對端發送數據(連接之前已經被建立);

4)close(fd)時,直接丟棄接收緩衝區未讀取的數據,並給對方發送一個RST,這個是由SO_LINGER選項來控制;

5)a重啓,收到b的保活探針,a發RST通知b。

MSL、TTL和RTT

MSL:Maximum Segment Lifetime報文最大生存時間

TTL:time to live生存時間,這個生存時間是由源主機設置初始值但不是存的具體時間。

RTT:round-trip time客戶端到服務器往返所花時間

5、C++

new與malloc的區別

1)申請的內存所在位置:new是在自由存儲區上爲對象動態分配內存空間,那麼自由存儲區是否是堆?取決於operator new的實現細節,自由存儲區不僅可以是,還可以是靜態存儲區,這都看operator new在哪裏爲對象分配內存。還有定位new可以不爲對象分配內存,只是簡單返回指針實參;malloc函數從堆上分配內存。

2)返回類型安全性:new操作符返回的是對象類型的指針,類型嚴格與對象匹配,無需進行類型轉換,故new是符合類型安全性的操作符;malloc內存分配成功返回void*,需要強制類型轉換將void*指針轉換成我們需要的類型。

3)內存分配失敗時的返回值:new會拋出bad_alloc異常,它不會返回NULL,所以需要使用異常機制(try...catch);malloc會返回NULL

4)是否需要指定內存大小:new不需要指定內存塊的大小,編譯器會根據類型信息自行計算,而malloc則需要顯式地指定所需內存地大小

5)是否調用構造函數/析構函數:new會;malloc不會。

6)對數組的處理:new對數組的支持體現在它會分別調用構造函數初始化每一個數組元素,釋放對象時爲每個對象調用析構函數;malloc它並不知道你在這塊內存上要放的是數組還是啥的別的東西,反正它就給你一塊原始的內存,在給你內存的地址就完事,所以如果動態分配一個數組的內存,還需要我們手動自定數組的大小。

7)new與malloc是否可以相互調用:operator new/operator delete的實現可以基於malloc;而malloc的實現不可以去調用new。

8)是否可以被重載:operator new/operator delete可以被重載;而malloc/free並不允許重載。

9)已分配內存得擴充:new沒有配套設施來擴充內存;malloc分配內存後,如果使用過程中發現內存不足,可以使用realloc函數進行內存重新分配實現內存的擴充。

10)客戶處理內存分配不足:operator new拋出異常以反映一個未獲得滿足的需求之前,它會先調用一個用戶指定的錯誤處理函數,這就是new_handler,new_handler是一個指針類型,指向一個沒有參數沒有返回值得函數,即錯誤處理函數,客戶需要調用set_new_handler(new_handler p);而malloc,客戶並不能夠去編程決定內存不足以分配時要幹什麼,只能看着malloc返回NULL。

使用引用減少拷貝構造函數使用次數

sizeof

sizeof(空類),答案是1B,而不是0B,我們在聲明該實例的時候,必須給實例在內存中分配一定的空間,否則無法使用該實例,由於空類型不含任何信息,故而所佔內存大小由編譯器決定。

切記:一旦類中有其他的佔用空間成員,則這1個字節就不在計算之內。

static變量不會佔用類的大小,因爲它存儲在靜態存儲區。

若d由b,c派生而來,b的大小是1,c的大小是4,則d的大小是8而不是5,這是因爲爲了提高實例在內存中的存取效率,類的大小往往被調整到系統的整數倍,並採取就近的法則,是哪個最近的倍數,就是該類的大小,所以類d的大小爲8個字節。

虛繼承

虛繼承是解決C++中多重繼承問題的一種手段,從不同途徑繼承來的同一個基類,會在子類中存在多份拷貝。這將存在兩個問題:1)浪費空間;2)存在二義性。

虛繼承底層原理與編譯器相關,一般通過虛基類指針vbptr,該指針指向了一個虛基類表,虛表中記錄了虛基類與直接繼承類的偏移地址,通過偏移地址,這樣就可以找到虛基類成員,而虛繼承也不用像普通多繼承那樣維持着公共基類的兩份拷貝,節省了存儲空間。

模板特例化與實例化

模板特例化:如果我們想對特定的數據類型執行不同的代碼(而不是通用模板),這種情況下就可以使用模板特例化。

函數模板特例化:當特例化一個函數模板時,必須爲原模板中的每個模板參數都提供實參。使用關鍵字template後跟一個空尖括號<>,即template <>,以指出我們正在特例化一個模板。

template<>
void fun(int a){}

類模板特例化

template<> //對int型特例化
class Test<int>{};

實例化:使用類時後面加<>指明具體類型。

棧溢出幾種情況

1)局部數組過大。當函數內部的數組過大時,有可能導致棧溢出;

2)遞歸調用層次太多。遞歸函數在運行時會執行壓棧操作,當壓棧次數太多時,也會導致堆棧溢出;

3)指針或數據越界。這種情況最常見,例如進行字符串拷貝,或處理用戶輸入等等。

模板與多態的使用場景

模板:STL標準庫中的容器就是模板類

多態:設計模式

 STL容器的線程安全

(1)線程安全的情況:1)多個讀者是安全的。多線程可能同時讀取一個容器的內容,這將正確地執行;2)對不同容器的多個寫入者是安全的。多線程可以同時寫不同的容器。

(2)線程不安全的情況:1)在對同一個容器進行多線程的讀寫、寫操作時;2)在每次調用容器的成員函數期間都要鎖定該容器;3)在每個容器返回的迭代器(例如通過調用begin或end)的生存期之內都要鎖定該容器;4)在每個在容器上調用的算法執行期間鎖定該容器。

C/C++中volatile關鍵字

1)爲什麼用volatile

volatile關鍵字和const對應,用來修飾變量,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。比如,操作系統、硬件或者其他線程。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。聲明語法:int volatile vInt;當要求使用volatile聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。

2)多線程下的volatile

防止優化編譯器把變量從內存裝入CPU寄存器中,volatile的意思是讓編譯器每次操作該變量時一定要從內存中真正取出,而不是使用已經存在寄存器中的值。

STL sort函數實現詳解

除了對普通的快速排序進行優化,它還結合了插入排序堆排序。根據不同的數量級以及不同情況,能自動選用合適的排序方法。當數據量較大時採用快速排序,分段遞歸。一旦分段後的數據小於某個閾值,爲避免遞歸調用帶來過大的額外負荷,便會改用插入排序。而如果遞歸層次過深,有出現最壞情況的傾向,還會改用堆排序

6、數據庫

基本操作

爲表中的字段添加索引alter table 表名 add index(字段)

多列索引:最左前綴匹配規則(a,b,c)相當於創建了(a)單列索引,(a,b)組合索引以及(a,b,c)組合索引。

增加一列:alter table 表名 add column 列名 varchar(20) not null after 列名;

刪除一列:alter table 表名 drop column 列名;

插入一行:insert into 表名 values( ,  ,  ,);

更新數據:update 表名 set a=b where c=' ';

刪除一行:delete from 表名 where a=' ';

建表:

create table tb1(
    id int not null,
    title varchar(100) not null,
);

刪除表:drop table db;

加鎖:共享鎖:select * from db where ... lock in share mode; 排他鎖:select ... for update;

一條SQL語句執行得很慢的原因有哪些?

(1)大多數情況下是正常的,只是偶爾會出現很慢的情況。

1)數據庫在同步數據到磁盤的時候。當我們要插入或更新一條數據時,數據庫會在內存中把對應字段的數據更新,但是更新後,這些更新的字段並不會馬上同步持久化到磁盤中,而是把這些更新的記錄寫入到redo log日誌中,等到空閒的時候,在通過redo log裏日記把最新的數據同步到磁盤中。不過,redo log裏的容量是有限的,如果數據庫一直很忙,更新又很頻繁,這個時候redo log很快會被寫滿,這個時候就沒辦法等到空閒的時候再把數據同步到磁盤的,只能暫停其他操作,全身心來把數據同步到磁盤中,而這個時候,就會導致我們平時正常的SQL語句突然執行的很慢。

2)拿不到鎖:我們要執行這條語句,剛好這條語句涉及到表,別人在用,並且加鎖了,我們拿不到鎖,只能慢慢等待別人釋放鎖了。或者,表沒有加鎖,但要使用到的某一個行被加鎖了。如果要判斷是否真的在等待鎖,我們可以用show processlist這個命令來查看當前的狀態。

(2)在數據量不變的情況下,這條SQL語句一直一來都執行的很慢。

1)沒有用到索引:你要查詢的字段沒有用索引,那麼只能走全表掃描了,導致這條查詢語句很慢。

2)字段有索引,但沒有用索引:select * from t where c-1=1000;

3)函數操作導致沒有用上索引

4)數據庫自己選錯索引:主鍵索引存放的值是整行字段的數據,而非主鍵索引上存放的是主鍵字段的值。所以在選擇時,會進行判斷是走索引的行數少?還是直接掃描全錶行數少?通過索引的區分度來預測判斷要掃描的行數,我們把區分度稱爲基數,即區分度越高,基數越大。通過採樣的方式來預測索引的基數。誤測基數很小,然後誤以爲索引的基數很小。

B+樹索引和hash索引的區別

1)如果是等值索引,那麼哈希索引明顯有絕對的優勢,因爲只需要一次算法即可找到相應的鍵值,當然前提是鍵值都是唯一的,如果鍵值不唯一,就需要先找到該鍵所在的位置,然後再根據鏈表往後掃描,直到找到相應的數據。

2)若是範圍查找,這時候hash索引就不行了,因爲原先有序的鍵值,經過哈希算法後,有可能變成了不連續的,就沒辦法利用索引完成範圍查詢檢索了。

3)hash索引也沒辦法利用索引完成排序,以及like 'xxx%'這樣的部分模糊查詢

4)hash索引也不支持多列聯合索引的最左匹配規則

5)B+樹索引的關鍵字檢索效率比較平均,不像B樹那樣波動幅度大,在有大量重複鍵值情況下,哈希索引的效率也是極低的,因爲存在所謂的哈希碰撞問題。

聚集索引和非聚集索引

聚集索引:就是對這堆記錄進行堆劃分,即主要描述的是物理上的存儲。即不同的書放到不同的房間。可以幫助把很大的範圍,迅速減小範圍。但是要查找該記錄,就要從這個小範圍中scan了。

非聚集索引:通過索引表查詢記錄所在的位置,然後通過位置去取要找的記錄。把一個很大的範圍,轉換成一個小的地圖。你需要在這個小地圖中找到你要尋找的信息的位置,然後通過這個位置,再去找你所需要的記錄。

 悲觀鎖與樂觀鎖

悲觀鎖:先獲取鎖,再進行業務操作。1)在對任何記錄進行修改之前,先嚐試爲該記錄加上排他鎖;2)如果加鎖失敗,說明記錄正在被修改,那麼當前查詢可能要等待或拋出異常;3)如果成功加鎖,則就可以對記錄做修改,事務完成後就會解鎖;4)期間如果有其他對記錄做修改或加排他鎖,都會等待我們解鎖或直接拋出異常。

特點:1)爲數據處理的安全提供了保證;2)效率上,由於處理加鎖的機制會讓數據庫產生額外開銷,增加產生死鎖機會;3)在只讀型事務中由於不會產生衝突,也沒必要使用鎖,這樣就會增加系統負載,降低並行性。

樂觀鎖:假設多用戶併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分數據,在提交數據更新之前,每個事務會先檢查在該事務讀取數據後,有沒有其他事務修改數據,如果有則回滾正在提交的事務。

特點:樂觀併發控制相信事務之間的數據競爭概率是較小的,因此儘可能直接做下去,直到提交的時候纔去鎖定,所以不會產生任何鎖和死鎖。

MySql主從複製原理

至少需要兩臺數據庫服務器,其中一臺爲Master庫,另一臺爲Slave庫,MySql主從數據同步是一個異步複製過程,要實現複製首先需要在master上開啓bin-log日誌功能,bin-log日誌用於記錄在Master庫中執行的增、刪、修改、更新操作的sql語句,整個過程需要開啓3個線程,分別是Master開啓IO線程,Slave開啓IO線程和SQL線程,Master的IO線程把bin-log發送給slave的IO線程,除了bin-log內容外,還有最新的bin-log文件名以及在bin-log中的下一個指定更新位置點。到Slave的中繼日誌relay-log日誌,用sql線程去檢測,若檢測到更新,則去Slave數據庫上執行一遍sql語句。

使用explain優化sql

對於複雜、效率低的sql語句,我們通常是使用explain sql來分析sql語句,這個語句可以打印處語句的執行過程,這樣方便我們分析,進行優化。possible_keys在試用了哪個索引在該表中查找行。

SQL連接(內連接、外連接、交叉連接)

內連接:等值連接和不等連接 =、>、<、<>、>=、<=、!>、!<

左連接:select * from db1 left join db2 on db1.id=db2.id; 左連接顯示左表全部行,和右表與左表相同的行。

右連接:right join ... on ... 顯示右表全部行,和左表與右表相同的行。

全連接:full join ... on ... 返回左表和右表中所有行,當某一行在另一表中沒有匹配行,則另一表中的列返回空值。

交叉連接,也稱笛卡爾積:select * from db1 cross join db2; 返回結果的行數等於兩個錶行數的乘積。

char/varchar/nvarchar

char:定長的,不足時補充空值,超過時截取超出的字符

varchar:可變長度,n必須是一個介於1和8000之間的數值,存儲大小爲輸入數據的字節實際長度,而不是n個字節

nvarchar:可變長度,n必須是一個介於1和4000之間,存儲大小是所輸入字符個數的兩倍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章