本篇文章給大家整理分享28道PHP面試題(附答案分享),帶你梳理基礎知識,有一定的參考價值,有需要的朋友可以參考一下,希望對大家有所幫助。
過完年之后打算尋找新的工作機會,發現之前自己對于很多基礎的面試理解和學習不夠深刻,為了鼓勵自己持續前進所以最近開始在論壇和搜索引擎上開始學習和總結相關知識,其中有一些題目時論壇里面一些前輩分享過的題目或者答案,還有一部分時自己最近面試遇到的問題,基于自己的理解和前輩們的分享歸檔了一部分,所以分享出來,希望對其他的小伙伴們也有幫助,同時也希望能收到大佬們對于理解有誤的地方的指導,最近一段時間會持續更新
本論壇的文章參考的比較多,優先列出來大家參考,如下: https://learnku.com/articles/63520 https://learnku.com/php/t/47623 https://learnku.com/articles/28772
一、php 數組底層實現原理
1、底層實現是通過散列表(hash table) + 雙向鏈表(解決hash沖突)
-
hashtable:將不同的關鍵字(key)通過映射函數計算得到散列值(Bucket->h) 從而直接索引到對應的Bucket
-
hash表保存當前循環的指針,所以foreach 比for更快
-
Bucket:保存數組元素的key和value,以及散列值h
2、如何保證有序性
-
1. 散列函數和元素數組(Bucket)中間添加一層大小和存儲元素數組相同的映射表。
-
2. 用于存儲元素在實際存儲數組中的下標
-
3. 元素按照映射表的先后順序插入實際存儲數組中
-
4. 映射表只是原理上的思路,實際上并不會有實際的映射表,而是初始化的時候分配Bucket內存的同時,還會分配相同數量的 uint32_t 大小的空間,然后將 arData 偏移到存儲元素數組的位置。
3、解決hash重復(php使用的鏈表法):
-
1. 鏈表法:不同關鍵字指向同一個單元時,使用鏈表保存關鍵字(遍歷鏈表匹配key)
-
2. 開放尋址法:當關鍵字指向已經存在數據的單元的時候,繼續尋找其他單元,直到找到可用單元(占用其他單元位置,更容易出現hash沖突,性能下降)
4、基礎知識
-
鏈表:隊列、棧、雙向鏈表、
-
鏈表 :元素 + 指向下一元素的指針
-
雙向鏈表:指向上一元素的指針 + 元素 + 指向下一元素的指針
參考:
算法的時間與空間復雜度(一看就懂)
https://zhuanlan.zhihu.com/p/50479555
LeetCode0:學習算法必備知識:時間復雜度與空間復雜度的計算
https://cloud.tencent.com/developer/article/1769988
二、冒泡排序的時間復雜度和空間復雜度
1、代碼實現
$arr = [2, 4, 1, 5, 3, 6]; for ($i = 0; $i < (count($arr)); $i++) { for ($j = $i + 1; $j < (count($arr)); $j++) { if ($arr[$i] <= $arr[$j]) { $temp = $arr[$i]; $arr[$i] = $arr[$j]; $arr[$j] = $temp; } } } result : [6,5,4,3,2,1]
2、計算原理
-
第一輪:將數組的第一個元素和其他所有的元素進行比較,哪個元素更大,就換順序,從而冒泡出第一大(最大)的元素
-
第一輪:將數組的第二個元素和其他所有的元素進行比較(第一大已經篩選出來不用繼續比較了),哪個元素更大,就換順序,從而冒泡出第二大的元素
-
… 依次類推,冒泡從大到小排序的數組
平均時間復雜度:O(n^2)
;
最優時間復雜度:O(n)
,需要加判斷,第一次循環如果一次都沒有交換就直接跳出循環
空間復雜度:O(1)
,交換元素的時候的臨時變量占用的空間
最優空間復雜度:O(1)
,排好序,不需要交換位置
3、時間復雜度和空間復雜度
時間復雜度:全程為漸進時間復雜度,估算對處理器的使用效率(描述算法的效率趨勢,并不是指算法具體使用的時間,因為不同機器的性能不一致,只是一種效率計算的通用方法)
表示方法:大O符號表示法
復雜度量級:
-
常數階O(1)
-
線性階O(n)
-
平方階O(n2)
-
立方階O(n3)
-
K次方階O(n^k)
-
指數階(2^n)
-
對數階O(logN)
-
線性對數階O(nlogN)
時間復制類型:
-
最好時間復雜度
-
最壞時間復雜度
-
平均時間復雜度
-
均攤時間復雜度
空間復雜度:全程漸進空間復雜度,估算對計算機內存的使用程度(描述算法占用的存儲空間的趨勢,不是實際占用空間,同上)
參考:
算法的時間與空間復雜度(一看就懂)
https://zhuanlan.zhihu.com/p/50479555
LeetCode0:學習算法必備知識:時間復雜度與空間復雜度的計算
https://cloud.tencent.com/developer/article/1769988
三、網絡七層協議及 TCP 和 TCP
應用層、表示層、會話層、傳輸層、網絡層、(數據)鏈路層、物理層
記憶套路:
首字:應表會傳(物鏈網)
第一個字:應用層(出現次數多,易憶)
前四個正向:應表 – 會傳
后三個反向:物聯網諧音比網鏈物更好記
四、TCP 和 UDP 的特點和區別
1、都是屬于傳輸層協議
2、TCP
-
面向連接,所以只能一對一
-
面向字節流傳輸
-
數據可靠,不丟失
-
全雙工通信
3、UDP(根據TCP特點反記)
-
無連接,支持一對一,一對多,多對多
-
面向保溫傳輸
-
首部開銷小,數據不一定可靠但是速度更快
參考:
github:colinlet/PHP-Interview-QA =>PHP 面試問答 網絡篇
https://github.com/colinlet/PHP-Interview-QA/blob/master/docs/01.網絡.md#1-計算機網絡體系結構
五、TCP 的三次握手和四次揮手
1、三次握手:
-
1)第一次:客戶端發送SYN = 1,seq = client_isn
作用:
客戶端:無
服務端:確認自己的接收功能和客戶端的發送功能
-
2)第二次:服務端發送SYN = 1,seq = server_isn,ACK =client_isn +1
作用:
客戶端:確認自己發送和接收都正常,確認服務端的接收和發送正常
服務端:確認自己的接收正常,確認服務端的發送正常(這時候服務端還不能確認客戶端接收是否正常)
-
3)第三次:客戶端發送SYN = 0, ACK = server_isn+1,seq =client_isn+1
作用:雙方確認互相的接收和發送正常,建立連接
2、四次揮手
-
1)第一次:客戶端發送FIN
作用:告訴服務端我沒有數據發送了(但是還能接收數據)
-
2)第二次:服務端發送ACK
作用:告訴客戶端收到請求了,可能服務端可能還有數據需要發送,所以客戶端收到進入FIN_WAIT狀態,等服務端數據傳輸完之后發送FIN
-
3)第三次:服務端發送FIN
作用:服務端告訴客戶端我發送完了,可以關閉連接了。
-
4)第四次:客戶端發送ACK
作用:客戶端收到FIN之后,擔心服務端不知道要關閉,所以發送一個ACK,進入TIME_WAIT,等待2MSL之后如果沒有收到回復,證明服務端已經關閉了,這時候客戶端也關閉連接。
注意:
-
當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據
-
最后需要等待2MSL是因為網絡是不可靠的,如果服務端沒有收到最后一次ACK,服務端會重新放FIN包然后等客戶端再次發送ACK包然后關閉(所以客戶端最后發送ACK之后不能立即關閉連接)
參考:
github:colinlet/PHP-Interview-QA =>PHP 面試問答 網絡篇
https://github.com/colinlet/PHP-Interview-QA/blob/master/docs/01.網絡.md#1-計算機網絡體系結構
TCP 協議中的三次握手和四次揮手 (圖解)
https://blog.csdn.net/whuslei/article/details/6667471
“三次握手,四次揮手” 你真的懂嗎?
https://zhuanlan.zhihu.com/p/53374516
TCP 的特性
https://hit-alibaba.github.io/interview/basic/network/TCP.html
六、HTTP 狀態碼
1、狀態碼分類
-
– 1xx:信息,服務器收到請求,需要請求者繼續操作
-
– 2xx:成功
-
– 3xx:重定向
-
– 4xx:客戶端錯誤
-
– 5xx:服務端錯誤
2、常用狀態碼
-
200:請求成功
-
301:永久重定向
-
302:臨時移動
-
400 bad request:客戶端請求語法錯誤
-
401 unauthorized:客戶端沒有權限
-
403 forbidden:服務器拒絕客戶端請求
-
404 not found:客戶端請求資源不存在
-
500 Internal Server Eerro:服務器內部錯誤
-
502 bad gateway:作為網關或者代理工作的服務器嘗試執行請求時,從上游服務器接收到無效的響應
-
503 Service Unavailable 超載或系統維護
-
504 Gateway timeout:網關超時
3、502 的原因及解決方法
原因:nginx將請求提交給網關(php-fpm)處理異常導致
1)fastcgi 緩沖區設置過小
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
2)php-cgi的進程數設置過少
查看FastCgi進程數:netstat -anpo | grep "php-cgi"| wc -l
調整參數最大子進程數:max_children
一般按照單個進程20M計算需要需要設置的子進程數
3)max_requests(內存溢出或頻繁重啟)
參數指明每個children最多能處理的請求數量,到達最大值之后會重啟children。
設置過小可能導致頻繁重啟children:
php將請求輪詢給每個children,在大流量的場景下,每一個children 到達最大值的時間差不多,如果設置過小可能多個children 在同一時間關閉,nginx無法將請求轉發給php-fpm,cpu降低,負載變高。
設置過大可能導致內存泄露
4)php執行時間超過nginx等待時間
fastcgi_connect_timeout
fastcgi_send_timeout
fastcgi_read_timeout
5)fastcgi執行時間
max_execution_time
參考:
php+php-fom+nginx 配置參數調優詳解
http://www.voycn.com/article/phpphp-fomnginxpeizhicanshudiaoyouxiangjie
nginx 報錯 502
https://www.cnblogs.com/fengzhongzhuzu/p/9193355.html
七、http 和 HTTPS 的區別
1、端口:http 80; https :443
2、http無狀態,https是有http + ssl構建的可進行加密傳輸的協議
3、http明文傳輸,https加密傳輸
4、http更快,三次握手三個包,https 需要12個包(3個tcp包+9個ssl握手包)
八、redis 分布式鎖及問題
1、實現:
加鎖:setnx
解鎖:del
鎖超時:expire
2、可能出現的問題
-
1)setnx 和expire非原子性問題(加鎖之后還沒來得及設置超時就掛了)
解決方案:
Redis 2.6.12以上版本為set指令增加了可選參數,偽代碼如下:set(key,1,30,NX),這樣就可以取代setnx指令
-
2)超時誤刪其他進程鎖。(A進程執行超時,導致鎖釋放,這時候B進程獲取鎖開始處理請求,這時候A進程處理完成,會誤刪B進程的鎖)
解決方案:只能刪除自己進程的鎖 (lua腳本防止B進程獲取過期鎖之后誤刪A進程的鎖)
-
3)并發場景,A進程執行超時導致鎖釋放,這時候B進程獲取到鎖。
解決方案:開啟守護進程,給當前進程要過期的鎖延時。
-
4)單點實例安全問題
單機宕機之后導致所有客戶端無法獲取鎖
解決:
主從復制,因為是異步完成的所以無法完全實現解決
參考:
使用 Redis 作為分布式鎖的一些注意點
https://www.cnblogs.com/gxyandwmm/p/9588383.html
面試官:你真的了解 Redis 分布式鎖嗎?
https://segmentfault.com/a/1190000038988087
九、redis 為什么是單線程?為什么快?
推薦閱讀:https://www.php.cn/redis/475918.html
十、redis 的數據類型及應用場景
1、string :
普通的key/value存儲
2、hash:
hashmap:鍵值隊集合,存儲對象信息
3、list:
雙向鏈表:消息隊列
4、set:
value永遠為null的hashMap:無序集合且不重復:計算交集、并集、差集、去重值
5、zset:
有序集合且不重復:hashMap(去重) + skiplist跳躍表(保證有序):排行榜
參考:
Redis 五種數據類型及應用場景
https://www.cnblogs.com/jasonZh/p/9513948.html
十一、redis 實現持久化的方式及原理、特點
1、RDB持久化(快照):指定時間間隔內的內存數據集快照寫入磁盤
1)fork一個子進程,將快照內容寫入臨時RDB文件中(dump.rdb),當子進程寫完快照內容之后新的文件替換老的文件
2)整個redis數據庫只包含一個備份文件
3)性能最大化,只需要fork子進程完成持久化工作,減少磁盤IO
4)持久化之前宕機可能會導致數據丟失
2、AOF持久化 :以日志的形式記錄服務器的所有的寫、刪除操作
1)每接收到一個寫的命令用write函數追加到文件appendonly.aof
2)持久化的文件會越來越大,存在大量多余的日志(0 自增100次到100,會產生100條日志記錄)
3)可以設置不同的fsync策略
-
appendfsync everysec :1s一次,最多丟失1s的數據(默認)
-
appendfsync always :每次變動都會執行一次
-
appendfsync no :不處理
4)AOF文件太大之后會進行重寫:壓縮AOF文件大小
-
fork一個子進程,將redis內地數據對象的最新狀態寫入AOF臨時文件(類似rdb快照)
-
主進程收到的變動會先寫入內存中,然后同步到老的AOF文件中(重寫失敗之后也能保證數據完整性)
-
子進程完成重寫之后會將內存中的新變動同步追加到AOF的臨時文件中
-
父進程將臨時AOF文件替換成新的AOF文件,并重命名。之后收到的新命令寫入到新的文件中
參考:
Redis 專題:萬字長文詳解持久化原理
https://segmentfault.com/a/1190000039208726
Redis 持久化
https://segmentfault.com/a/1190000002906345
RDB 和 AOF 持久化的原理是什么?我應該用哪一個?它們的優缺點?
https://segmentfault.com/a/1190000018388385
十二、秒殺設計流程及難點
1、靜態緩存
2、nginx 負載均衡
三種方式:DNS輪詢、IP負債均衡、CDN
3、限流機制
方式:ip限流、接口令牌限流、用戶限流、header動態token(前端加密,后端解密)
4、分布式鎖
方式:
-
setnx + expire (非原子性,redis2.6 之后set保證原子性)
-
釋放鎖超時 (開啟守護進程自動續時間)
-
過期鎖誤刪其他線程(requestId驗證或者lua腳本保證查 + 刪的原子性)
5、緩存數據
方式:
-
緩存擊穿:緩存數據預熱 + 布隆過濾器/空緩存
-
緩存雪崩:緩存設置隨機過期時間,防止同一時間過期
6、庫存及訂單
-
扣庫存
-
redis 自減庫存,并發場景下可能導致負數,影響庫存回倉:使用lua腳本保證原子性
-
redis預扣庫存之后,然后使用異步消息創建訂單并更新庫存變動
-
數據庫更新庫存使用樂觀鎖:where stock_num – sell_num > 0
-
添加消息發送記錄表及重試機制,防止異步消息丟失
-
-
創建訂單
-
前端建立websocket連接或者輪詢監聽訂單狀態
-
消費驗證記錄狀態,防止重復消費
-
-
回倉
-
創建訂單之后發送延時消息,驗證訂單支付狀態及庫存是否需要回倉
-
十三、防 sql 注入
1、過濾特殊字符
2、過濾數據庫關鍵字
3、驗證數據類型及格式
4、使用預編譯模式,綁定變量
十四、事務隔離級別
1、標準的sql隔離級別實現原理
-
未提交讀:其他事務可以直接讀到沒有提交的:臟讀
-
事務對當前被讀取的數據不加鎖
-
在更新的瞬間加行級共享鎖到事務結束釋放
-
-
提交讀:事務開始和結束之間讀取的數據可能不一致,事務中其他事務修改了數據:不可重復度
-
事務對當前讀取的數據(被讀到的時候)行級共享鎖,讀完釋放
-
在更新的瞬間加行級排他鎖到事務結束釋放
-
-
可重復讀:事務開始和結束之前讀取的數據保持一致,事務中其他事務不能修改數據:可重復讀
-
事務對當前讀到的數據從事務一開始就加一個行級共享鎖
-
在更新的瞬間加行級排他鎖到事務結束釋放
-
其他事務再事務過程中可能會新增數據導致幻讀
-
-
串行化
-
事務讀取數據時加表級共享鎖
-
事務更新數據時加表級排他鎖
-
2、innodb的事務隔離級別及實現原理(!!和上面的不一樣,區分理解一個是隔離級別 一個是!!事務!!隔離級別)
1)基本概念
-
mvcc:多版本并發控制:依賴于undo log 和read view
-
讓數據都讀不會對數據加鎖,提高數據庫并發處理能力
-
寫操作才會加鎖
-
一條數據有多個版本,每次事務更新數據的時候會生成一個新的數據版本,舊的數據保留在undo log
-
一個事務啟動的時候只能看到所有已經提交的事務結果
-
-
當前讀:讀取的是最新版本
-
快照讀:讀取的是歷史版本
-
間隙鎖:間隙鎖會鎖住一個范圍內的索引
-
update id between 10 and 20
-
無論是否范圍內是否存在數據,都會鎖住整個范圍:insert id = 15,將被防止
-
只有可重復讀隔離級別才有間隙鎖
-
-
next-key lock:
-
索引記錄上的記錄鎖+ 間隙鎖(索引值到前一個索引值之間的間隙鎖)
-
前開后閉
-
阻止幻讀
-
2)事務隔離級別
-
未提交讀
-
事務對當前讀取的數據不加鎖,都是當前讀
-
在更新的瞬間加行級共享鎖到事務結束釋放
-
-
提交讀
-
事務對當前讀取的數據不加鎖,都是快照讀
-
在更新的瞬間加行級排他鎖到事務結束釋放
-
-
可重復讀
-
事務對當前讀取的數據不加鎖,都是快照讀
-
事務再更新某數據的瞬間,必須加行級排他鎖(Record 記錄鎖、GAP間隙鎖、next-key 鎖),事務結束釋放
-
間隙鎖解決的是幻讀問題
-
主從復制的情況下 ,如果沒有間隙鎖,master庫的A、B進程
-
A進程 delete id < 6 ;然后還沒有commit
-
B進程insert id = 3,commit
-
A進程提交commit
-
該場景下,主庫會存在一條id =3 的記錄,但是binlog里面是先刪除再新增就會導致從庫沒有數據,導致主從的數據不一致
-
-
MVCC的快照解決的是不可重復讀問題
-
-
串行化
-
事務讀取數據時加表級,當前讀
-
事務更新數據時加表級排他鎖
-
參考:
深入理解 MySQL 中事務隔離級別的實現原理
https://segmentfault.com/a/1190000025156465
小胖問我:MySQL 事務與 MVCC 原理
https://segmentfault.com/a/1190000039809030
快照在 MVCC 中是怎么工作的?
https://www.jianshu.com/p/6c4454ffecc3
MVCC 我知道,但是為什么要設計間隙鎖?
https://www.jianshu.com/p/fbec6d1fa16c
間隙鎖和 next-key lock
https://www.jianshu.com/p/d1aba64b5c03
十五、索引原理
索引就是幫助數據庫高效查找數據的存儲結構,存儲再磁盤中,需要消耗磁盤IO
1、存儲引擎
-
myisam 支持表鎖,索引和數據分開存儲適合跨服務器遷移
-
innodb 支持行鎖,索引和數據存儲再一個文件
2、索引類型
-
hash索引
-
適合精確查詢且效率高
-
無法排序、不適合范圍查詢
-
hash沖突的情況下需要遍歷鏈表(php數組的實現原理、redis zset 的實現原理類似)
-
-
b-tree、b+tree
-
b-tree 和b+tree的去區別
-
b+tree 的數據全部存儲在葉子節點,內部節點只存key,一次磁盤IO能獲取到
-
-