C++編程經驗(10):無鎖編程其實沒那么玄乎
801
2022-05-29
小伙伴們,大家新年好,今天給大家分享字節跳動抖音電商的面經,希望對小伙伴們有所幫助~
面試官:你好,我是字節跳動的面試官xxx,請問是大彬嗎?
大彬:面試官,您好,我是大彬
面試官:現在方便面試嗎?
大彬:嗯嗯,可以的
面試官:那我們現在開始面試吧
面試官:看你簡歷上寫了熟悉集合相關內容,你了解HashMap嗎?講一下HashMap的put方法?
獨白:果然一上來就是HashMap…
大彬:HashMap 實現了Map接口,用于保存鍵值對映射。其底層是使用數組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實現的。
大彬:它的put方法過程如下:
如果table沒有初始化就先進行初始化過程
使用hash算法計算key的索引
判斷索引處有沒有存在元素,沒有就直接插入
如果索引處存在元素,則遍歷插入,有兩種情況,一種是鏈表形式就直接遍歷到尾端插入,一種是紅黑樹就按照紅黑樹結構插入
鏈表的數量大于閾值8,就要轉換成紅黑樹的結構
添加成功后會檢查是否需要擴容
面試官:嗯,剛剛你提到HashMap的擴容,詳細講一下?
獨白:emm,給自己挖坑了…
大彬:以JDK1.8為例,當往HashMap放入元素時,如果元素個數大于threshold時,會進行擴容,使用2倍容量的數組代替原有數組。
大彬:由于數組的容量是以2的冪次方擴容的,那么一個Entity在擴容時,新的位置要么在原位置,要么在原長度+原位置的位置。
大彬:原因是數組長度變為原來的2倍,表現在二進制上就是多了一個高位參與數組下標計算。
大彬:也就是說,在元素拷貝過程不需要重新計算元素在數組中的位置,只需要看看原來的hash值新增的那個bit是1還是0,是0的話索引沒變,是1的話索引變成“原索引+oldCap”(根據e.hash & (oldCap - 1) == 0判斷) 。
大彬:這樣可以省去重新計算hash值的時間,而且由于新增的1bit是0還是1可以認為是隨機的,因此resize的過程會均勻的把之前的沖突的節點分散到新的bucket。
面試官:小伙子,基礎還算不錯。看你簡歷上寫了精通MySQL,來講講一下MySQL的索引結構?
獨白:臥槽,以后再也不敢寫精通了…還好昨天背了大廠面試手冊,現在一點都不慌。
大彬:MySQL 數據庫使用最多的索引類型是BTREE索引,底層基于B+樹數據結構來實現。
大彬:B+ 樹是基于B 樹和葉子節點順序訪問指針進行實現,它具有B樹的平衡性,并且通過順序訪問指針來提高區間查詢的性能。
大彬:進行查找操作時,首先在根節點進行二分查找,找到key所在的指針,然后遞歸地在指針所指向的節點進行查找。直到查找到葉子節點,然后在葉子節點上進行二分查找,找出 key 所對應的數據項。
面試官:為什么索引要用B+樹來實現呢,而不是用二叉樹?
大彬:B+樹有個特點,就是夠矮夠胖,能有效地減少訪問節點次數從而提高性能。
大彬:雖然二叉樹也有很好的查找性能log2N,但是當N比較大的時候,樹的深度比較高。數據查詢的時間主要依賴于磁盤IO的次數,二叉樹深度越大,查找的次數越多,性能越差。最壞的情況會退化成鏈表。所以,B+樹更適合作為MySQL索引結構。
面試官:那又為什么不用B樹呢?
獨白:現在面試也太卷了趴,這是要造火箭啊…
大彬:因為B樹的分支結點存儲著數據,我們要找到具體的數據,需要進行一次中序遍歷按序來掃。而由于B+樹的數據都存儲在葉子結點中,葉子結點均為索引,方便掃庫,只需要掃一遍葉子結點即可。所以B+樹更加適合在區間查詢的情況,而在數據庫中基于范圍的查詢是非常頻繁的,所以B+樹更適合用于數據庫索引。
面試官:知道聚集索引嗎?
大彬:聚集索引嚴格來說并不是索引類型,而是一種數據存儲方式,具體細節依賴于其實現方式。如innodb聚集索引的葉子節點存放了整張表的行記錄。
大彬:聚集索引類似字典的拼音目錄。表中的數據按照聚集索引的規則來存儲的。就像新華字典,整本字典是按照A-Z的順序來排列。這也是一個表只能有一個聚集索引的原因。
面試官:那聚簇索引相比非聚簇索引有什么優點?
大彬:1. 數據訪問更快,因為聚簇索引將索引和數據保存在同一個B+樹中,因此從聚簇索引中獲取數據比非聚簇索引更快。
大彬:2. 聚集索引葉子節點的存儲是邏輯上連續的,所以對于主鍵的排序查找和范圍查找速度會更快。
面試官:嗯,問點其他的,線程池知道吧?
大彬:線程池,顧名思義,就是一個管理線程的池子。
面試官:那為什么使用線程池呢?
大彬:之所以使用線程池,主要有三點原因:
降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
提高響應速度。當任務到達時,可以不需要等到線程創建就能立即執行。
提高線程的可管理性。統一管理線程,避免系統創建大量同類線程而導致消耗完內存。
面試官:嗯,那你講一下線程池的幾個參數?
獨白:老八股文了嘿嘿~
大彬:先來看看ThreadPoolExecutor 的通用構造函數:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
大彬:其中有7個參數。分別是corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler
大彬:corePoolSize。當有新任務時,如果線程池中線程數沒有達到線程池的基本大小,則會創建新的線程執行任務,否則將任務放入阻塞隊列。當線程池中存活的線程數總是大于 corePoolSize 時,應該考慮調大 corePoolSize。
大彬:maximumPoolSize。當阻塞隊列填滿時,如果線程池中線程數沒有超過最大線程數,則會創建新的線程運行任務。否則根據拒絕策略處理新任務。非核心線程類似于臨時借來的資源,這些線程在空閑時間超過 keepAliveTime 之后,就應該退出,避免資源浪費。
大彬:BlockingQueue。存儲等待運行的任務。
大彬:keepAliveTime。非核心線程空閑后,保持存活的時間,此參數只對非核心線程有效。設置為0,表示多余的空閑線程會被立即終止。
大彬:TimeUnit。時間單位,具體如下:
TimeUnit.DAYS TimeUnit.HOURS TimeUnit.MINUTES TimeUnit.SECONDS TimeUnit.MILLISECONDS TimeUnit.MICROSECONDS TimeUnit.NANOSECONDS
大彬:ThreadFactory。每當線程池創建一個新的線程時,都是通過線程工廠方法來完成的。在 ThreadFactory 中只定義了一個方法 newThread,每當線程池需要創建新線程就會調用它。
public class MyThreadFactory implements ThreadFactory { private final String poolName; public MyThreadFactory(String poolName) { this.poolName = poolName; } public Thread newThread(Runnable runnable) { return new MyAppThread(runnable, poolName);//將線程池名字傳遞給構造函數,用于區分不同線程池的線程 } }
大彬:RejectedExecutionHandler。當隊列和線程池都滿了時,根據拒絕策略處理新任務。
AbortPolicy:默認的策略,直接拋出RejectedExecutionException DiscardPolicy:不處理,直接丟棄 DiscardOldestPolicy:將等待隊列隊首的任務丟棄,并執行當前任務 CallerRunsPolicy:由調用線程處理該任務
面試官:好的。你了解 Spring AOP 嗎?
大彬:AOP,其實就是面向切面編程,將一些公共邏輯(事務管理、日志、緩存等)封裝成切面,跟業務代碼進行分離,可以減少系統的重復代碼和降低模塊之間的耦合度。切面就是那些與業務無關,但所有業務模塊都會調用的公共邏輯。
大彬:Spring AOP是通過動態代理技術實現的。
面試官:哦,那動態代理的實現方式有哪些?
大彬:動態代理技術的實現方式有兩種:
基于接口的 JDK 動態代理。
基于繼承的 CGLib 動態代理。在Spring中,如果目標類沒有實現接口,那么Spring AOP會選擇使用CGLIB來動態代理目標類。
面試官:你剛剛提到CGlib動態代理,能詳細介紹下嗎?
大彬:CGLIB,就是Code Generator Library,它是一個強大的、高性能的代碼生成庫,被廣泛應用于AOP框架中,用以提供方法攔截操作。
大彬:CGLIB代理主要通過對字節碼的操作,為對象引入間接級別,以控制對象的訪問。
大彬:CGLib 動態代理相對于 JDK 動態代理局限性就小很多,目標對象不需要實現接口,底層是通過繼承目標對象產生代理子對象。
面試官:不錯,好好準備二面吧~
碼字不易,如果覺得對你有幫助,可以點個贊鼓勵一下!
Java
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。