圖解學(xué)習(xí)網(wǎng)站:https://xiaolincoding.com
大家好,我是小林。
有同學(xué)問我有沒有寫過電信的面經(jīng)?我翻了一下我的記錄,發(fā)現(xiàn)還沒寫過中國電信的面經(jīng),但是寫過中國移動(dòng)的面經(jīng)。
中國電信、移動(dòng)和聯(lián)通是國內(nèi)三大運(yùn)營商了,都屬于國企,每年也是一樣都有開發(fā)崗位的招聘需求,國企的優(yōu)勢就是加班不多,相比互聯(lián)網(wǎng)公司能早下班,而且也更加穩(wěn)定一些。
那薪資待遇如何呢?
中國電信給校招軟開崗的總包是?20 萬-25 萬(一線城市),二線城市的話是 15 萬左右,不過總包是算上各種福利總和(比如獎(jiǎng)金、公積金啥的),每個(gè)月實(shí)際到手會(huì)比私企 20w 總包的低一些。
之前訓(xùn)練營也有校招同學(xué)拿到一線城市中國電信 offer,開的是總包 21w。
二線城市的中國電信開的是 15w 總包
這次跟大家聊聊中國移動(dòng)的軟開一二面面經(jīng),中國移動(dòng)面試流程是 2 輪面試,面試的時(shí)長都不算長:
- 第一輪技術(shù)面面了 30 分鐘,拷打了操作系統(tǒng)、網(wǎng)絡(luò)、Redis、Java 方面的知識(shí)第二輪則是技術(shù)面+hr 面一起了,時(shí)長 20 分鐘,技術(shù)問的問題比多,主要是拷打了 操作系統(tǒng)+數(shù)據(jù)結(jié)構(gòu)方面的內(nèi)容,剩下了就是聊天局了。
具體的面試題如下:
中國電信(一面)
1. 信號(hào)了解嗎,平時(shí)接觸過哪些信號(hào)?
信號(hào)是進(jìn)程間通信的一種方式,它是操作系統(tǒng)向進(jìn)程發(fā)送的一種異步通知機(jī)制,用于告知進(jìn)程發(fā)生了某個(gè)特定事件。進(jìn)程可以根據(jù)接收到的信號(hào)采取相應(yīng)的行動(dòng),如暫停執(zhí)行、繼續(xù)執(zhí)行、終止進(jìn)程等。
平時(shí)接觸過的信號(hào)有:
SIGINT(中斷信號(hào))
-
- :通常由用戶按下 Ctrl+C 組合鍵產(chǎn)生,用于通知進(jìn)程需要中斷當(dāng)前的操作并終止運(yùn)行。例如,在命令行中運(yùn)行一個(gè)程序時(shí),按下 Ctrl+C 可以向該程序進(jìn)程發(fā)送 SIGINT 信號(hào),使其停止運(yùn)行。
SIGTERM(終止信號(hào))
-
- :這是一種正常的終止信號(hào),用于通知進(jìn)程優(yōu)雅地結(jié)束運(yùn)行。與 SIGINT 不同,SIGTERM 通常由系統(tǒng)或其他進(jìn)程發(fā)送,而不是由用戶直接觸發(fā)。進(jìn)程接收到 SIGTERM 信號(hào)后,應(yīng)該進(jìn)行一些清理工作,如關(guān)閉打開的文件、釋放資源等,然后再終止。許多服務(wù)程序在接收到 SIGTERM 信號(hào)后會(huì)逐步停止正在處理的任務(wù),并關(guān)閉連接,以確保系統(tǒng)的穩(wěn)定性和數(shù)據(jù)的完整性。
SIGKILL(強(qiáng)制終止信號(hào))
- :這是一種強(qiáng)制終止進(jìn)程的信號(hào),不允許進(jìn)程進(jìn)行任何清理操作,直接將進(jìn)程終止。通常在進(jìn)程出現(xiàn)嚴(yán)重錯(cuò)誤或無法正常終止時(shí)使用。例如,當(dāng)一個(gè)進(jìn)程陷入死循環(huán)或占用大量系統(tǒng)資源且無法通過其他方式停止時(shí),可以使用 SIGKILL 信號(hào)來強(qiáng)制結(jié)束該進(jìn)程。不過,由于 SIGKILL 信號(hào)不會(huì)給進(jìn)程留下清理資源的機(jī)會(huì),可能會(huì)導(dǎo)致一些資源泄漏或數(shù)據(jù)不一致的問題,所以應(yīng)該謹(jǐn)慎使用。
2. 操作系統(tǒng)中,進(jìn)程同步與數(shù)據(jù)傳輸有什么方法,各自有什么特點(diǎn)?
進(jìn)程同步的方法有下面這些:
信號(hào)量
-
- :信號(hào)量是一個(gè)整數(shù)變量,用于控制多個(gè)進(jìn)程對(duì)共享資源的訪問。它可以實(shí)現(xiàn)更復(fù)雜的同步策略,如控制并發(fā)訪問的進(jìn)程數(shù)量。當(dāng)信號(hào)量的值大于 0 時(shí),進(jìn)程可以獲取信號(hào)量并訪問資源,同時(shí)信號(hào)量的值減 1;當(dāng)信號(hào)量的值為 0 時(shí),進(jìn)程需要等待。信號(hào)量可以分為二進(jìn)制信號(hào)量(只能取 0 和 1)和計(jì)數(shù)信號(hào)量(可以取任意非負(fù)整數(shù))。優(yōu)點(diǎn)是能靈活控制資源的并發(fā)訪問,適用于多種復(fù)雜的同步場景。缺點(diǎn)是如果使用不當(dāng),也可能出現(xiàn)死鎖或資源分配不合理的情況。
-
- :互斥鎖是一種簡單的進(jìn)程同步機(jī)制。當(dāng)一個(gè)進(jìn)程訪問共享資源時(shí),它會(huì)獲取互斥鎖,其他進(jìn)程則必須等待,直到該進(jìn)程釋放鎖。這種方法能確保在任何時(shí)刻只有一個(gè)進(jìn)程可以訪問共享資源,實(shí)現(xiàn)了對(duì)資源的互斥訪問。優(yōu)點(diǎn)是實(shí)現(xiàn)簡單,適用于對(duì)少量資源的短期鎖定。缺點(diǎn)是如果一個(gè)進(jìn)程在持有鎖期間出現(xiàn)異常或長時(shí)間不釋放鎖,可能導(dǎo)致其他進(jìn)程長時(shí)間等待,甚至出現(xiàn)死鎖。
條件變量:
- 條件變量通常與互斥鎖一起使用,用于實(shí)現(xiàn)進(jìn)程間的條件等待。一個(gè)進(jìn)程可以在某個(gè)條件滿足時(shí)通過條件變量通知其他等待的進(jìn)程。它允許進(jìn)程在等待特定條件發(fā)生時(shí)阻塞,避免了忙等待,從而提高了系統(tǒng)的效率。優(yōu)點(diǎn)是能有效實(shí)現(xiàn)進(jìn)程間的協(xié)作,減少不必要的 CPU 開銷。缺點(diǎn)是需要與互斥鎖配合使用,編程相對(duì)復(fù)雜,且對(duì)條件的判斷和通知機(jī)制需要謹(jǐn)慎處理,否則可能導(dǎo)致程序出現(xiàn)邏輯錯(cuò)誤。
進(jìn)程傳輸數(shù)據(jù)方法:
管道:
-
- 管道是一種半雙工的通信方式,用于在具有親緣關(guān)系的進(jìn)程之間傳輸數(shù)據(jù)。通常由父進(jìn)程創(chuàng)建管道,然后 fork 出子進(jìn)程,子進(jìn)程和父進(jìn)程通過管道進(jìn)行數(shù)據(jù)傳輸。數(shù)據(jù)在管道中是單向流動(dòng)的,一端用于寫入數(shù)據(jù),另一端用于讀取數(shù)據(jù)。管道的優(yōu)點(diǎn)是簡單易用,能方便地實(shí)現(xiàn)進(jìn)程間的通信。缺點(diǎn)是只能在有親緣關(guān)系的進(jìn)程間使用,且半雙工的特性限制了數(shù)據(jù)傳輸?shù)撵`活性,同時(shí)管道的容量有限,如果寫入速度過快可能導(dǎo)致數(shù)據(jù)堵塞。
消息隊(duì)列:
-
- 消息隊(duì)列是一種異步的通信機(jī)制,進(jìn)程可以向消息隊(duì)列中發(fā)送消息,其他進(jìn)程可以從消息隊(duì)列中讀取消息。消息隊(duì)列中的消息具有一定的格式和優(yōu)先級(jí),不同進(jìn)程可以根據(jù)自己的需求從隊(duì)列中獲取消息。它允許進(jìn)程在不同的時(shí)間進(jìn)行通信,不需要實(shí)時(shí)等待對(duì)方的響應(yīng)。優(yōu)點(diǎn)是通信具有異步性和松耦合性,適用于不同步執(zhí)行的進(jìn)程間通信。缺點(diǎn)是消息的大小和隊(duì)列的長度可能受到系統(tǒng)限制,而且如果消息處理不及時(shí),可能導(dǎo)致隊(duì)列溢出。
共享內(nèi)存:
-
- 共享內(nèi)存是一種高效的進(jìn)程間通信方式,它允許多個(gè)進(jìn)程共享同一塊內(nèi)存區(qū)域。進(jìn)程可以直接讀寫共享內(nèi)存中的數(shù)據(jù),無需進(jìn)行數(shù)據(jù)的復(fù)制。這種方式大大提高了數(shù)據(jù)傳輸?shù)男剩绕溥m用于大量數(shù)據(jù)的共享。但是,由于多個(gè)進(jìn)程可以同時(shí)訪問共享內(nèi)存,因此需要配合同步機(jī)制(如互斥鎖、信號(hào)量等)來保證數(shù)據(jù)的一致性和完整性。優(yōu)點(diǎn)是數(shù)據(jù)傳輸效率高,能快速共享大量數(shù)據(jù)。缺點(diǎn)是需要復(fù)雜的同步機(jī)制來確保數(shù)據(jù)的正確性,編程難度較大,且對(duì)共享內(nèi)存的訪問控制需要謹(jǐn)慎處理,否則可能導(dǎo)致數(shù)據(jù)混亂。
套接字:
- 套接字是一種網(wǎng)絡(luò)通信機(jī)制,不僅可以用于不同主機(jī)上的進(jìn)程間通信,也可以用于同一主機(jī)上的進(jìn)程間通信。它提供了一種基于網(wǎng)絡(luò)協(xié)議的通信方式,支持多種通信協(xié)議,如 TCP 和 UDP。通過套接字,進(jìn)程可以發(fā)送和接收數(shù)據(jù)報(bào),實(shí)現(xiàn)雙向的數(shù)據(jù)傳輸。套接字的優(yōu)點(diǎn)是具有很強(qiáng)的通用性和靈活性,能適應(yīng)不同的網(wǎng)絡(luò)環(huán)境和通信需求。缺點(diǎn)是網(wǎng)絡(luò)通信存在一定的延遲和不確定性,而且需要處理網(wǎng)絡(luò)故障、數(shù)據(jù)丟失等問題,編程實(shí)現(xiàn)相對(duì)復(fù)雜。
3. 了解過哪些網(wǎng)絡(luò)協(xié)議?
- 應(yīng)用層:https、https、dns 協(xié)議傳輸層:tcp、ip 協(xié)議網(wǎng)絡(luò)層:ip 協(xié)議
4. 說一下https加密過程?
傳統(tǒng)的 TLS 握手基本都是使用 RSA 算法來實(shí)現(xiàn)密鑰交換的,在將 TLS 證書部署服務(wù)端時(shí),證書文件其實(shí)就是服務(wù)端的公鑰,會(huì)在 TLS 握手階段傳遞給客戶端,而服務(wù)端的私鑰則一直留在服務(wù)端,一定要確保私鑰不能被竊取。
在 RSA 密鑰協(xié)商算法中,客戶端會(huì)生成隨機(jī)密鑰,并使用服務(wù)端的公鑰加密后再傳給服務(wù)端。根據(jù)非對(duì)稱加密算法,公鑰加密的消息僅能通過私鑰解密,這樣服務(wù)端解密后,雙方就得到了相同的密鑰,再用它加密應(yīng)用消息。
我用 Wireshark 工具抓了用 RSA 密鑰交換的 TLS 握手過程,你可以從下面看到,一共經(jīng)歷了四次握手:
TLS 第一次握手
首先,由客戶端向服務(wù)器發(fā)起加密通信請(qǐng)求,也就是 ClientHello 請(qǐng)求。在這一步,客戶端主要向服務(wù)器發(fā)送以下信息:
- (1)客戶端支持的 TLS 協(xié)議版本,如 TLS 1.2 版本。(2)客戶端生產(chǎn)的隨機(jī)數(shù)(Client Random),后面用于生成「會(huì)話秘鑰」條件之一。(3)客戶端支持的密碼套件列表,如 RSA 加密算法。
TLS 第二次握手
服務(wù)器收到客戶端請(qǐng)求后,向客戶端發(fā)出響應(yīng),也就是 SeverHello。服務(wù)器回應(yīng)的內(nèi)容有如下內(nèi)容:
- (1)確認(rèn) TLS 協(xié)議版本,如果瀏覽器不支持,則關(guān)閉加密通信。(2)服務(wù)器生產(chǎn)的隨機(jī)數(shù)(Server Random),也是后面用于生產(chǎn)「會(huì)話秘鑰」條件之一。(3)確認(rèn)的密碼套件列表,如 RSA 加密算法。(4)服務(wù)器的數(shù)字證書。
TLS 第三次握手
客戶端收到服務(wù)器的回應(yīng)之后,首先通過瀏覽器或者操作系統(tǒng)中的 CA 公鑰,確認(rèn)服務(wù)器的數(shù)字證書的真實(shí)性。
如果證書沒有問題,客戶端會(huì)從數(shù)字證書中取出服務(wù)器的公鑰,然后使用它加密報(bào)文,向服務(wù)器發(fā)送如下信息:
- (1)一個(gè)隨機(jī)數(shù)(pre-master key)。該隨機(jī)數(shù)會(huì)被服務(wù)器公鑰加密。(2)加密通信算法改變通知,表示隨后的信息都將用「會(huì)話秘鑰」加密通信。(3)客戶端握手結(jié)束通知,表示客戶端的握手階段已經(jīng)結(jié)束。這一項(xiàng)同時(shí)把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個(gè)摘要,用來供服務(wù)端校驗(yàn)。
上面第一項(xiàng)的隨機(jī)數(shù)是整個(gè)握手階段的第三個(gè)隨機(jī)數(shù),會(huì)發(fā)給服務(wù)端,所以這個(gè)隨機(jī)數(shù)客戶端和服務(wù)端都是一樣的。
服務(wù)器和客戶端有了這三個(gè)隨機(jī)數(shù)(Client Random、Server Random、pre-master key),接著就用雙方協(xié)商的加密算法,各自生成本次通信的「會(huì)話秘鑰」。
TLS 第四次握手
服務(wù)器收到客戶端的第三個(gè)隨機(jī)數(shù)(pre-master key)之后,通過協(xié)商的加密算法,計(jì)算出本次通信的「會(huì)話秘鑰」。
然后,向客戶端發(fā)送最后的信息:
- (1)加密通信算法改變通知,表示隨后的信息都將用「會(huì)話秘鑰」加密通信。(2)服務(wù)器握手結(jié)束通知,表示服務(wù)器的握手階段已經(jīng)結(jié)束。這一項(xiàng)同時(shí)把之前所有內(nèi)容的發(fā)生的數(shù)據(jù)做個(gè)摘要,用來供客戶端校驗(yàn)。
至此,整個(gè) TLS 的握手階段全部結(jié)束。接下來,客戶端與服務(wù)器進(jìn)入加密通信,就完全是使用普通的 HTTP 協(xié)議,只不過用「會(huì)話秘鑰」加密內(nèi)容。
5. 使用緩存時(shí)可能會(huì)產(chǎn)生什么問題,如何解決?
-
- 可能會(huì)出現(xiàn)緩存與數(shù)據(jù)庫數(shù)據(jù)不一致的問題,導(dǎo)致業(yè)務(wù)邏輯錯(cuò)誤,比如更新數(shù)據(jù)庫后,緩存未同步或同步失敗。解決方式通過Canal監(jiān)聽數(shù)據(jù)庫Binlog,異步更新緩存,保證最終一致性。熱點(diǎn) key 問題,某個(gè)Key的訪問量極高,導(dǎo)致緩存服務(wù)器單點(diǎn)壓力過大,解決方式將一個(gè)熱點(diǎn)Key拆分為多個(gè)子Key(如
hot_key:1
-
- 、
hot_key:2
- ),分散到不同節(jié)點(diǎn)。大 key 的問題,單個(gè)Key存儲(chǔ)的數(shù)據(jù)過大(如10MB的List),導(dǎo)致網(wǎng)絡(luò)阻塞、查詢緩慢,解決方式將大Key拆分為多個(gè)小Key,或使用壓縮算法(如Gzip)減少體積。緩存異常的問題,比如緩存擊穿、穿透、雪崩的問題
6. 緩存擊穿,穿透,雪崩是什么,如何預(yù)防?
緩存雪崩:當(dāng)大量緩存數(shù)據(jù)在同一時(shí)間過期(失效)或者 Redis 故障宕機(jī)時(shí),如果此時(shí)有大量的用戶請(qǐng)求,都無法在 Redis 中處理,于是全部請(qǐng)求都直接訪問數(shù)據(jù)庫,從而導(dǎo)致數(shù)據(jù)庫的壓力驟增,嚴(yán)重的會(huì)造成數(shù)據(jù)庫宕機(jī),從而形成一系列連鎖反應(yīng),造成整個(gè)系統(tǒng)崩潰,這就是緩存雪崩的問題。
緩存擊穿:如果緩存中的某個(gè)熱點(diǎn)數(shù)據(jù)過期了,此時(shí)大量的請(qǐng)求訪問了該熱點(diǎn)數(shù)據(jù),就無法從緩存中讀取,直接訪問數(shù)據(jù)庫,數(shù)據(jù)庫很容易就被高并發(fā)的請(qǐng)求沖垮,這就是緩存擊穿的問題。
緩存穿透:當(dāng)用戶訪問的數(shù)據(jù),既不在緩存中,也不在數(shù)據(jù)庫中,導(dǎo)致請(qǐng)求在訪問緩存時(shí),發(fā)現(xiàn)緩存缺失,再去訪問數(shù)據(jù)庫時(shí),發(fā)現(xiàn)數(shù)據(jù)庫中也沒有要訪問的數(shù)據(jù),沒辦法構(gòu)建緩存數(shù)據(jù),來服務(wù)后續(xù)的請(qǐng)求。那么當(dāng)有大量這樣的請(qǐng)求到來時(shí),數(shù)據(jù)庫的壓力驟增,這就是緩存穿透的問題。
緩存雪崩解決方案:
均勻設(shè)置過期時(shí)間:如果要給緩存數(shù)據(jù)設(shè)置過期時(shí)間,應(yīng)該避免將大量的數(shù)據(jù)設(shè)置成同一個(gè)過期時(shí)間。我們可以在對(duì)緩存數(shù)據(jù)設(shè)置過期時(shí)間時(shí),給這些數(shù)據(jù)的過期時(shí)間加上一個(gè)隨機(jī)數(shù),這樣就保證數(shù)據(jù)不會(huì)在同一時(shí)間過期。
互斥鎖:當(dāng)業(yè)務(wù)線程在處理用戶請(qǐng)求時(shí),如果發(fā)現(xiàn)訪問的數(shù)據(jù)不在 Redis 里,就加個(gè)互斥鎖,保證同一時(shí)間內(nèi)只有一個(gè)請(qǐng)求來構(gòu)建緩存(從數(shù)據(jù)庫讀取數(shù)據(jù),再將數(shù)據(jù)更新到 Redis 里),當(dāng)緩存構(gòu)建完成后,再釋放鎖。未能獲取互斥鎖的請(qǐng)求,要么等待鎖釋放后重新讀取緩存,要么就返回空值或者默認(rèn)值。
實(shí)現(xiàn)互斥鎖的時(shí)候,最好設(shè)置超時(shí)時(shí)間,不然第一個(gè)請(qǐng)求拿到了鎖,然后這個(gè)請(qǐng)求發(fā)生了某種意外而一直阻塞,一直不釋放鎖,這時(shí)其他請(qǐng)求也一直拿不到鎖,整個(gè)系統(tǒng)就會(huì)出現(xiàn)無響應(yīng)的現(xiàn)象。后臺(tái)更新緩存:業(yè)務(wù)線程不再負(fù)責(zé)更新緩存,緩存也不設(shè)置有效期,而是讓緩存“永久有效”,并將更新緩存的工作交由后臺(tái)線程定時(shí)更新。
緩存擊穿解決方案:
- 互斥鎖方案,保證同一時(shí)間只有一個(gè)業(yè)務(wù)線程更新緩存,未能獲取互斥鎖的請(qǐng)求,要么等待鎖釋放后重新讀取緩存,要么就返回空值或者默認(rèn)值。不給熱點(diǎn)數(shù)據(jù)設(shè)置過期時(shí)間,由后臺(tái)異步更新緩存,或者在熱點(diǎn)數(shù)據(jù)準(zhǔn)備要過期前,提前通知后臺(tái)線程更新緩存以及重新設(shè)置過期時(shí)間;
緩存穿透解決方案:
- 非法請(qǐng)求的限制:當(dāng)有大量惡意請(qǐng)求訪問不存在的數(shù)據(jù)的時(shí)候,也會(huì)發(fā)生緩存穿透,因此在 API 入口處我們要判斷求請(qǐng)求參數(shù)是否合理,請(qǐng)求參數(shù)是否含有非法值、請(qǐng)求字段是否存在,如果判斷出是惡意請(qǐng)求就直接返回錯(cuò)誤,避免進(jìn)一步訪問緩存和數(shù)據(jù)庫。緩存空值或者默認(rèn)值:當(dāng)我們線上業(yè)務(wù)發(fā)現(xiàn)緩存穿透的現(xiàn)象時(shí),可以針對(duì)查詢的數(shù)據(jù),在緩存中設(shè)置一個(gè)空值或者默認(rèn)值,這樣后續(xù)請(qǐng)求就可以從緩存中讀取到空值或者默認(rèn)值,返回給應(yīng)用,而不會(huì)繼續(xù)查詢數(shù)據(jù)庫。布隆過濾器:我們可以在寫入數(shù)據(jù)庫數(shù)據(jù)時(shí),使用布隆過濾器做個(gè)標(biāo)記,然后在用戶請(qǐng)求到來時(shí),業(yè)務(wù)線程確認(rèn)緩存失效后,可以通過查詢布隆過濾器快速判斷數(shù)據(jù)是否存在,如果不存在,就不用通過查詢數(shù)據(jù)庫來判斷數(shù)據(jù)是否存在。即使發(fā)生了緩存穿透,大量請(qǐng)求只會(huì)查詢 Redis 和布隆過濾器,而不會(huì)查詢數(shù)據(jù)庫,保證了數(shù)據(jù)庫能正常運(yùn)行,Redis 自身也是支持布隆過濾器的。
7. 有一組服務(wù)器,現(xiàn)在來了一個(gè)任務(wù),希望實(shí)現(xiàn)一個(gè)均衡調(diào)度算法,有什么思路?
輪詢調(diào)度
-
- :依次將任務(wù)分配給每臺(tái)服務(wù)器,簡單公平,但未考慮服務(wù)器負(fù)載差異
加權(quán)輪詢
-
- :根據(jù)服務(wù)器性能分配不同權(quán)重,高性能服務(wù)器獲得更多任務(wù),缺點(diǎn)權(quán)重值的設(shè)置需要根據(jù)服務(wù)器的實(shí)際情況進(jìn)行調(diào)整,且難以動(dòng)態(tài)適應(yīng)服務(wù)器負(fù)載的變化。
最少連接
-
- :將新任務(wù)分配給當(dāng)前連接數(shù)最少的服務(wù)器,這樣可以確保任務(wù)被分配到相對(duì)空閑的服務(wù)器上,避免某些服務(wù)器過載
加權(quán)最少連接算法
- :加權(quán)最少連接算法結(jié)合了加權(quán)輪詢算法和最少連接算法的優(yōu)點(diǎn)。在考慮服務(wù)器當(dāng)前連接數(shù)的同時(shí),還考慮了服務(wù)器的性能權(quán)重。計(jì)算每臺(tái)服務(wù)器的 “加權(quán)連接數(shù)”(通常為當(dāng)前連接數(shù)除以權(quán)重值),將新任務(wù)分配給加權(quán)連接數(shù)最小的服務(wù)器。
8. Java的容器了解嗎,用過哪些,適用的場景?
List是有序的Collection,使用此接口能夠精確的控制每個(gè)元素的插入位置,用戶能根據(jù)索引訪問List中元素。常用的實(shí)現(xiàn)List的類有LinkedList,ArrayList,Vector,Stack。
- ArrayList 是容量可變的非線程安全列表,其底層使用數(shù)組實(shí)現(xiàn)。當(dāng)發(fā)生擴(kuò)容時(shí),會(huì)創(chuàng)建更大的數(shù)組,并把原數(shù)組復(fù)制到新數(shù)組。ArrayList支持對(duì)元素的快速隨機(jī)訪問,但插入與刪除速度很慢。LinkedList本質(zhì)是一個(gè)雙向鏈表,與ArrayList相比,其插入和刪除速度更快,但隨機(jī)訪問速度更慢。Vector 與 ArrayList 類似,底層也是基于數(shù)組實(shí)現(xiàn),特點(diǎn)是線程安全,但效率相對(duì)較低,因?yàn)槠浞椒ù蠖啾?synchronized 修飾
Map?是一個(gè)鍵值對(duì)集合,存儲(chǔ)鍵、值和之間的映射。Key 無序,唯一;value 不要求有序,允許重復(fù)。Map 沒有繼承于 Collection 接口,從 Map 集合中檢索元素時(shí),只要給出鍵對(duì)象,就會(huì)返回對(duì)應(yīng)的值對(duì)象。主要實(shí)現(xiàn)有TreeMap、HashMap、HashTable、LinkedHashMap、ConcurrentHashMap
- HashMap:JDK1.8 之前 HashMap 由數(shù)組+鏈表組成的,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的(“拉鏈法”解決沖突),JDK1.8 以后在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長度大于閾值(默認(rèn)為 8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間LinkedHashMap:LinkedHashMap 繼承自 HashMap,所以它的底層仍然是基于拉鏈?zhǔn)缴⒘薪Y(jié)構(gòu)即由數(shù)組和鏈表或紅黑樹組成。另外,LinkedHashMap 在上面結(jié)構(gòu)的基礎(chǔ)上,增加了一條雙向鏈表,使得上面的結(jié)構(gòu)可以保持鍵值對(duì)的插入順序。同時(shí)通過對(duì)鏈表進(jìn)行相應(yīng)的操作,實(shí)現(xiàn)了訪問順序相關(guān)邏輯。HashTable:數(shù)組+鏈表組成的,數(shù)組是 HashMap 的主體,鏈表則是主要為了解決哈希沖突而存在的,HashTable 是線程安全的,其方法使用 synchronized 修飾,且不允許 null 鍵和 null 值。TreeMap:紅黑樹(自平衡的排序二叉樹)實(shí)現(xiàn),Key 按自然順序或 Comparator 排序。ConcurrentHashMap:采用 Node 數(shù)組 + 鏈表 + 紅黑樹實(shí)現(xiàn),是線程安全的。JDK1.8 以前使用 Segment 鎖(分段鎖),JDK1.8 以后使用 volatile + CAS 或者 synchronized 保證線程安全。其中,Segment 鎖是將整個(gè)數(shù)據(jù)結(jié)構(gòu)分成多個(gè)段,每個(gè)段有獨(dú)立的鎖,不同段的操作可以并發(fā)進(jìn)行;volatile 保證變量的可見性,CAS 是一種無鎖算法,synchronized 用于對(duì)鏈表或紅黑樹進(jìn)行加鎖操作。
Set不允許存在重復(fù)的元素,與List不同,Set中的元素是無序的(TreeSet 除外)。常用的實(shí)現(xiàn)有HashSet,LinkedHashSet和TreeSet。
- HashSet通過HashMap實(shí)現(xiàn),HashMap的Key即HashSet存儲(chǔ)的元素,所有Key都是用相同的Value,一個(gè)名為PRESENT的Object類型常量。使用Key保證元素唯一性,但不保證有序性。由于HashSet是HashMap實(shí)現(xiàn)的,因此線程不安全。LinkedHashSet繼承自HashSet,通過LinkedHashMap實(shí)現(xiàn),使用雙向鏈表維護(hù)元素插入順序。TreeSet通過TreeMap實(shí)現(xiàn)的,添加元素到集合時(shí)按照比較規(guī)則將其插入合適的位置,保證插入后的集合仍然有序。
9. Java的垃圾回收機(jī)制,你知道哪些?
標(biāo)記-清除算法
-
- :標(biāo)記-清除算法分為“標(biāo)記”和“清除”兩個(gè)階段,首先通過可達(dá)性分析,標(biāo)記出所有需要回收的對(duì)象,然后統(tǒng)一回收所有被標(biāo)記的對(duì)象。標(biāo)記-清除算法有兩個(gè)缺陷,一個(gè)是效率問題,標(biāo)記和清除的過程效率都不高,另外一個(gè)就是,清除結(jié)束后會(huì)造成大量的碎片空間。有可能會(huì)造成在申請(qǐng)大塊內(nèi)存的時(shí)候因?yàn)闆]有足夠的連續(xù)空間導(dǎo)致再次 GC。
復(fù)制算法
-
- :為了解決碎片空間的問題,出現(xiàn)了“復(fù)制算法”。復(fù)制算法的原理是,將內(nèi)存分成兩塊,每次申請(qǐng)內(nèi)存時(shí)都使用其中的一塊,當(dāng)內(nèi)存不夠時(shí),將這一塊內(nèi)存中所有存活的復(fù)制到另一塊上。然后將然后再把已使用的內(nèi)存整個(gè)清理掉。復(fù)制算法解決了空間碎片的問題。但是也帶來了新的問題。因?yàn)槊看卧谏暾?qǐng)內(nèi)存時(shí),都只能使用一半的內(nèi)存空間。內(nèi)存利用率嚴(yán)重不足。
標(biāo)記-整理算法
-
- :復(fù)制算法在 GC 之后存活對(duì)象較少的情況下效率比較高,但如果存活對(duì)象比較多時(shí),會(huì)執(zhí)行較多的復(fù)制操作,效率就會(huì)下降。而老年代的對(duì)象在 GC 之后的存活率就比較高,所以就有人提出了“標(biāo)記-整理算法”。標(biāo)記-整理算法的“標(biāo)記”過程與“標(biāo)記-清除算法”的標(biāo)記過程一致,但標(biāo)記之后不會(huì)直接清理。而是將所有存活對(duì)象都移動(dòng)到內(nèi)存的一端。移動(dòng)結(jié)束后直接清理掉剩余部分。
分代回收算法
- :分代收集是將內(nèi)存劃分成了新生代和老年代。分配的依據(jù)是對(duì)象的生存周期,或者說經(jīng)歷過的 GC 次數(shù)。對(duì)象創(chuàng)建時(shí),一般在新生代申請(qǐng)內(nèi)存,當(dāng)經(jīng)歷一次 GC 之后如果對(duì)還存活,那么對(duì)象的年齡 +1。當(dāng)年齡超過一定值(默認(rèn)是 15,可以通過參數(shù) -XX:MaxTenuringThreshold 來設(shè)定)后,如果對(duì)象還存活,那么該對(duì)象會(huì)進(jìn)入老年代。
10. 項(xiàng)目和其他
- 介紹項(xiàng)目經(jīng)歷,比較大的挑戰(zhàn),如何解決項(xiàng)目里有哪些優(yōu)化,后續(xù)有什么可以深一步拓展的未來的職業(yè)規(guī)劃反問
中國電信(二面)
1. 一個(gè)數(shù)組,原地建堆,時(shí)間復(fù)雜度?
原地建堆的時(shí)間復(fù)雜度為?O(n),具體分析如下。
假設(shè)數(shù)組的長度為?n
,且堆是一棵完全二叉樹,節(jié)點(diǎn)總數(shù)與高度關(guān)系:
設(shè)堆的高度為h,樹的層數(shù)為h = log?n
。第k
層的節(jié)點(diǎn)數(shù)為n/(2^k)
,該層每個(gè)節(jié)點(diǎn)需要最多調(diào)整k
次(從下往上調(diào)整)。
總調(diào)整次數(shù)計(jì)算,公式如下:
等比數(shù)列求和結(jié)果為?O(2n)
,因此?T(n) = O(n)
。
不同層級(jí)的調(diào)整次數(shù):
高度為1
的節(jié)點(diǎn):調(diào)整次數(shù)為1 × n/2
。
高度為2
的節(jié)點(diǎn):調(diào)整次數(shù)為2 × n/4
??偤蜑?code>n/2 + n/4 + n/8 + ... ≈ 2n ?O(n)
。
Java 原地建堆的算法實(shí)現(xiàn)如下:
public?class?HeapBuilder?{
? ??// 下沉操作(以最大堆為例)
? ??private?static?void?siftDown(int[] arr,?int?i,?int?n)?{
? ? ? ??while?(i < n) {
? ? ? ? ? ??int?left =?2?* i +?1;
? ? ? ? ? ??int?right =?2?* i +?2;
? ? ? ? ? ??int?largest = i;
? ? ? ? ? ??if?(left < n && arr[left] > arr[largest]) {
? ? ? ? ? ? ? ? largest = left;
? ? ? ? ? ? }
? ? ? ? ? ??if?(right < n && arr[right] > arr[largest]) {
? ? ? ? ? ? ? ? largest = right;
? ? ? ? ? ? }
? ? ? ? ? ??if?(largest != i) {
? ? ? ? ? ? ? ??// 交換元素
? ? ? ? ? ? ? ??int?temp = arr[i];
? ? ? ? ? ? ? ? arr[i] = arr[largest];
? ? ? ? ? ? ? ? arr[largest] = temp;
? ? ? ? ? ? ? ? i = largest; ?// 繼續(xù)下沉
? ? ? ? ? ? }?else?{
? ? ? ? ? ? ? ??break; ?// 調(diào)整完成
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ??// 原地建堆
? ??public?static?void?buildHeap(int[] arr)?{
? ? ? ??int?n = arr.length;
? ? ? ??// 從最后一個(gè)非葉節(jié)點(diǎn)開始反向遍歷
? ? ? ??for?(int?i = n /?2?-?1; i >=?0; i--) {
? ? ? ? ? ? siftDown(arr, i, n);
? ? ? ? }
? ? }
? ??public?static?void?main(String[] args)?{
? ? ? ??int[] arr = {3,?5,?1,?7,?2,?9,?4};
? ? ? ? buildHeap(arr); ?// 結(jié)果: [9,7,4,5,3,1,2]
? ? }
}
2. 操作系統(tǒng)的進(jìn)程調(diào)度算法有哪些
先來先服務(wù)調(diào)度算法
最簡單的一個(gè)調(diào)度算法,就是非搶占式的先來先服務(wù)算法了。
顧名思義,先來后到,每次從就緒隊(duì)列選擇最先進(jìn)入隊(duì)列的進(jìn)程,然后一直運(yùn)行,直到進(jìn)程退出或被阻塞,才會(huì)繼續(xù)從隊(duì)列中選擇第一個(gè)進(jìn)程接著運(yùn)行。
這似乎很公平,但是當(dāng)一個(gè)長作業(yè)先運(yùn)行了,那么后面的短作業(yè)等待的時(shí)間就會(huì)很長,不利于短作業(yè)。 FCFS 對(duì)長作業(yè)有利,適用于 CPU 繁忙型作業(yè)的系統(tǒng),而不適用于 I/O 繁忙型作業(yè)的系統(tǒng)。
最短作業(yè)優(yōu)先調(diào)度算法
最短作業(yè)優(yōu)先調(diào)度算法同樣也是顧名思義,它會(huì)優(yōu)先選擇運(yùn)行時(shí)間最短的進(jìn)程來運(yùn)行,這有助于提高系統(tǒng)的吞吐量。
這顯然對(duì)長作業(yè)不利,很容易造成一種極端現(xiàn)象。
比如,一個(gè)長作業(yè)在就緒隊(duì)列等待運(yùn)行,而這個(gè)就緒隊(duì)列有非常多的短作業(yè),那么就會(huì)使得長作業(yè)不斷的往后推,周轉(zhuǎn)時(shí)間變長,致使長作業(yè)長期不會(huì)被運(yùn)行。
高響應(yīng)比優(yōu)先調(diào)度算法
前面的「先來先服務(wù)調(diào)度算法」和「最短作業(yè)優(yōu)先調(diào)度算法」都沒有很好的權(quán)衡短作業(yè)和長作業(yè)。
那么,高響應(yīng)比優(yōu)先調(diào)度算法主要是權(quán)衡了短作業(yè)和長作業(yè)。
每次進(jìn)行進(jìn)程調(diào)度時(shí),先計(jì)算「響應(yīng)比優(yōu)先級(jí)」,然后把「響應(yīng)比優(yōu)先級(jí)」最高的進(jìn)程投入運(yùn)行,「響應(yīng)比優(yōu)先級(jí)」的計(jì)算公式:
從上面的公式,可以發(fā)現(xiàn):
- 如果兩個(gè)進(jìn)程的「等待時(shí)間」相同時(shí),「要求的服務(wù)時(shí)間」越短,「響應(yīng)比」就越高,這樣短作業(yè)的進(jìn)程容易被選中運(yùn)行;如果兩個(gè)進(jìn)程「要求的服務(wù)時(shí)間」相同時(shí),「等待時(shí)間」越長,「響應(yīng)比」就越高,這就兼顧到了長作業(yè)進(jìn)程,因?yàn)檫M(jìn)程的響應(yīng)比可以隨時(shí)間等待的增加而提高,當(dāng)其等待時(shí)間足夠長時(shí),其響應(yīng)比便可以升到很高,從而獲得運(yùn)行的機(jī)會(huì);
時(shí)間片輪轉(zhuǎn)調(diào)度算法
最古老、最簡單、最公平且使用最廣的算法就是時(shí)間片輪轉(zhuǎn)調(diào)度算法。
每個(gè)進(jìn)程被分配一個(gè)時(shí)間段,稱為時(shí)間片,即允許該進(jìn)程在該時(shí)間段中運(yùn)行。
- 如果時(shí)間片用完,進(jìn)程還在運(yùn)行,那么將會(huì)把此進(jìn)程從 CPU 釋放出來,并把 CPU 分配另外一個(gè)進(jìn)程;如果該進(jìn)程在時(shí)間片結(jié)束前阻塞或結(jié)束,則 CPU 立即進(jìn)行切換;
另外,時(shí)間片的長度就是一個(gè)很關(guān)鍵的點(diǎn):
- 如果時(shí)間片設(shè)得太短會(huì)導(dǎo)致過多的進(jìn)程上下文切換,降低了 CPU 效率;如果設(shè)得太長又可能引起對(duì)短作業(yè)進(jìn)程的響應(yīng)時(shí)間變長。將
通常時(shí)間片設(shè)為?20ms~50ms
?通常是一個(gè)比較合理的折中值。
最高優(yōu)先級(jí)調(diào)度算法
前面的「時(shí)間片輪轉(zhuǎn)算法」做了個(gè)假設(shè),即讓所有的進(jìn)程同等重要,也不偏袒誰,大家的運(yùn)行時(shí)間都一樣。
但是,對(duì)于多用戶計(jì)算機(jī)系統(tǒng)就有不同的看法了,它們希望調(diào)度是有優(yōu)先級(jí)的,即希望調(diào)度程序能從就緒隊(duì)列中選擇最高優(yōu)先級(jí)的進(jìn)程進(jìn)行運(yùn)行,這稱為最高優(yōu)先級(jí)調(diào)度算法。 進(jìn)程的優(yōu)先級(jí)可以分為,靜態(tài)優(yōu)先級(jí)或動(dòng)態(tài)優(yōu)先級(jí):
-
-
- 靜態(tài)優(yōu)先級(jí):創(chuàng)建進(jìn)程時(shí)候,就已經(jīng)確定了優(yōu)先級(jí)了,然后整個(gè)運(yùn)行時(shí)間優(yōu)先級(jí)都不會(huì)變化;動(dòng)態(tài)優(yōu)先級(jí):根據(jù)進(jìn)程的動(dòng)態(tài)變化調(diào)整優(yōu)先級(jí),比如如果進(jìn)程運(yùn)行時(shí)間增加,則降低其優(yōu)先級(jí),如果進(jìn)程等待時(shí)間(就緒隊(duì)列的等待時(shí)間)增加,則升高其優(yōu)先級(jí),也就是
隨著時(shí)間的推移增加等待進(jìn)程的優(yōu)先級(jí)
- 。
-
該算法也有兩種處理優(yōu)先級(jí)高的方法,非搶占式和搶占式:
- 非搶占式:當(dāng)就緒隊(duì)列中出現(xiàn)優(yōu)先級(jí)高的進(jìn)程,運(yùn)行完當(dāng)前進(jìn)程,再選擇優(yōu)先級(jí)高的進(jìn)程。搶占式:當(dāng)就緒隊(duì)列中出現(xiàn)優(yōu)先級(jí)高的進(jìn)程,當(dāng)前進(jìn)程掛起,調(diào)度優(yōu)先級(jí)高的進(jìn)程運(yùn)行。
但是依然有缺點(diǎn),可能會(huì)導(dǎo)致低優(yōu)先級(jí)的進(jìn)程永遠(yuǎn)不會(huì)運(yùn)行。
多級(jí)反饋隊(duì)列調(diào)度算法
多級(jí)反饋隊(duì)列調(diào)度算法是「時(shí)間片輪轉(zhuǎn)算法」和「最高優(yōu)先級(jí)算法」的綜合和發(fā)展。
顧名思義:
- 「多級(jí)」表示有多個(gè)隊(duì)列,每個(gè)隊(duì)列優(yōu)先級(jí)從高到低,同時(shí)優(yōu)先級(jí)越高時(shí)間片越短?!阜答仭贡硎救绻行碌倪M(jìn)程加入優(yōu)先級(jí)高的隊(duì)列時(shí),立刻停止當(dāng)前正在運(yùn)行的進(jìn)程,轉(zhuǎn)而去運(yùn)行優(yōu)先級(jí)高的隊(duì)列;
來看看,它是如何工作的:
設(shè)置了多個(gè)隊(duì)列,賦予每個(gè)隊(duì)列不同的優(yōu)先級(jí),每個(gè)隊(duì)列優(yōu)先級(jí)從高到低,同時(shí)優(yōu)先級(jí)越高時(shí)間片越短;新的進(jìn)程會(huì)被放入到第一級(jí)隊(duì)列的末尾,按先來先服務(wù)的原則排隊(duì)等待被調(diào)度,如果在第一級(jí)隊(duì)列規(guī)定的時(shí)間片沒運(yùn)行完成,則將其轉(zhuǎn)入到第二級(jí)隊(duì)列的末尾,以此類推,直至完成;當(dāng)較高優(yōu)先級(jí)的隊(duì)列為空,才調(diào)度較低優(yōu)先級(jí)的隊(duì)列中的進(jìn)程運(yùn)行。如果進(jìn)程運(yùn)行時(shí),有新進(jìn)程進(jìn)入較高優(yōu)先級(jí)的隊(duì)列,則停止當(dāng)前運(yùn)行的進(jìn)程并將其移入到原隊(duì)列末尾,接著讓較高優(yōu)先級(jí)的進(jìn)程運(yùn)行;
可以發(fā)現(xiàn),對(duì)于短作業(yè)可能可以在第一級(jí)隊(duì)列很快被處理完。
對(duì)于長作業(yè),如果在第一級(jí)隊(duì)列處理不完,可以移入下次隊(duì)列等待被執(zhí)行,雖然等待的時(shí)間變長了,但是運(yùn)行時(shí)間也會(huì)更長了,所以該算法很好的兼顧了長短作業(yè),同時(shí)有較好的響應(yīng)時(shí)間。
3. 進(jìn)程,線程,協(xié)程是什么?
首先,我們來談?wù)?strong>進(jìn)程。進(jìn)程是操作系統(tǒng)中進(jìn)行資源分配和調(diào)度的基本單位,它擁有自己的獨(dú)立內(nèi)存空間和系統(tǒng)資源。每個(gè)進(jìn)程都有獨(dú)立的堆和棧,不與其他進(jìn)程共享。進(jìn)程間通信需要通過特定的機(jī)制,如管道、消息隊(duì)列、信號(hào)量等。由于進(jìn)程擁有獨(dú)立的內(nèi)存空間,因此其穩(wěn)定性和安全性相對(duì)較高,但同時(shí)上下文切換的開銷也較大,因?yàn)樾枰4婧突謴?fù)整個(gè)進(jìn)程的狀態(tài)。
接下來是線程。線程是進(jìn)程內(nèi)的一個(gè)執(zhí)行單元,也是CPU調(diào)度和分派的基本單位。與進(jìn)程不同,線程共享進(jìn)程的內(nèi)存空間,包括堆和全局變量。線程之間通信更加高效,因?yàn)樗鼈兛梢灾苯幼x寫共享內(nèi)存。線程的上下文切換開銷較小,因?yàn)橹恍枰4婧突謴?fù)線程的上下文,而不是整個(gè)進(jìn)程的狀態(tài)。然而,由于多個(gè)線程共享內(nèi)存空間,因此存在數(shù)據(jù)競爭和線程安全的問題,需要通過同步和互斥機(jī)制來解決。
最后是協(xié)程。協(xié)程是一種用戶態(tài)的輕量級(jí)線程,其調(diào)度完全由用戶程序控制,而不需要內(nèi)核的參與。協(xié)程擁有自己的寄存器上下文和棧,但與其他協(xié)程共享堆內(nèi)存。協(xié)程的切換開銷非常小,因?yàn)橹恍枰4婧突謴?fù)協(xié)程的上下文,而無需進(jìn)行內(nèi)核級(jí)的上下文切換。這使得協(xié)程在處理大量并發(fā)任務(wù)時(shí)具有非常高的效率。然而,協(xié)程需要程序員顯式地進(jìn)行調(diào)度和管理,相對(duì)于線程和進(jìn)程來說,其編程模型更為復(fù)雜。