對線面試官 | 位元組跳動一面

程式設計師大彬發表於2022-02-05

小夥伴們,大家新年好,今天給大家分享位元組跳動抖音電商的面經,希望對小夥伴們有所幫助~

面試官:你好,我是位元組跳動的面試官xxx,請問是大彬嗎?

大彬:面試官,您好,我是大彬

面試官:現在方便麵試嗎?

大彬:嗯嗯,可以的

面試官:那我們現在開始面試吧

面試官看你簡歷上寫了熟悉集合相關內容,你瞭解HashMap嗎?講一下HashMap的put方法?

獨白:果然一上來就是HashMap...

大彬:HashMap 實現了Map介面,用於儲存鍵值對對映。其底層是使用陣列+連結串列+紅黑樹(JDK1.8增加了紅黑樹部分)實現的。

大彬:它的put方法過程如下:

  1. 如果table沒有初始化就先進行初始化過程
  2. 使用hash演算法計算key的索引
  3. 判斷索引處有沒有存在元素,沒有就直接插入
  4. 如果索引處存在元素,則遍歷插入,有兩種情況,一種是連結串列形式就直接遍歷到尾端插入,一種是紅黑樹就按照紅黑樹結構插入
  5. 連結串列的數量大於閾值8,就要轉換成紅黑樹的結構
  6. 新增成功後會檢查是否需要擴容

面試官嗯,剛剛你提到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<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);

大彬:其中有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是通過動態代理技術實現的。

面試官哦,那動態代理的實現方式有哪些?

大彬:動態代理技術的實現方式有兩種:

  1. 基於介面的 JDK 動態代理。
  2. 基於繼承的 CGLib 動態代理。在Spring中,如果目標類沒有實現介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。

面試官你剛剛提到CGlib動態代理,能詳細介紹下嗎?

大彬:CGLIB,就是Code Generator Library,它是一個強大的、高效能的程式碼生成庫,被廣泛應用於AOP框架中,用以提供方法攔截操作。

大彬:CGLIB代理主要通過對位元組碼的操作,為物件引入間接級別,以控制物件的訪問。

大彬:CGLib 動態代理相對於 JDK 動態代理侷限性就小很多,目標物件不需要實現介面,底層是通過繼承目標物件產生代理子物件

面試官:不錯,好好準備二面吧~

碼字不易,如果覺得對你有幫助,可以點個贊鼓勵一下!

相關文章