字節跳動面試題


Java

1.ThreadLocal

ThreadLocal實現線程本地存儲的功能,同一個ThreadLocal所包含的對象,在不同的Thread中有不同的實例,獲取ThreadLocal對象時其實是在Thread類中的Map類型的threadLocals變量中通過ThreadLocal變量為鍵值進行獲取。

2.volatile的作用和原理

被volatile修飾的變量保證Java內存模型中的可見性和有序性。

  • 可見性:當一個線程修改了一個被volatile修飾的變量的值,新值會立即被刷新到主內存中,其他線程可以立即得知新值。
  • 有序性:禁止進行指令重排序。

volaitle底層是通過內存屏障來實現可見性和有序性。內存屏障是一個CPU的指令,他的作用有兩個,一是保證特定操作的執行順序,二是保證某些變量的內存可見性。內存屏障告訴編譯器和CPU,不管什么指令都不能和這條內存屏障指令重排序,另一個作用是強制刷出各種CPU的緩存資源,因此任何CPU上的線程都能讀取到這些數據的最新版本。

3.J.U.C中的鎖

Java提供了兩種鎖機制來控制多個線程對共享資源的互斥訪問。

  實現 公平鎖 等待可中斷 條件 性能
synchronized JVM 非公平 不可中斷 / 大致
ReentrantLock JDK 非公平/公平 可中斷 可綁定多個Condition 相同

除非要使用ReentrantLock的高級功能,否則優先使用synchronized,synchronized是JVM實現的一種鎖機制,JVM原生的支持它,而ReentrantLock不是所有的JDK版本都支持。synchronized鎖釋放由JVM保證,ReentrantLock需要顯式的釋放。

4.atomic包里的一些問題

atomic是使用volatile和CAS來實現的

5.HashMap的擴容

當HashMap的容量到達threshold時,需要進行動態擴容,將容量擴大為原來的兩倍,然后將存儲的數據進行rehash。

6.Semaphore信號量用來做什么?

Semaphore信號量類似於操作系統的信號量,可以控制對互斥資源的訪問線程數。

7.Java內存模型

CPU和內存之間增加高速緩存。

所有的變量都存儲在主內存中,每個線程有自己的工作內存,工作內存存儲在高速緩存中,保存了該線程使用變量的拷貝。

線程只能直接操作工作內存中的變量,不同線程之間的變量值傳遞需要通過主內存來完成。

內存模型的三大特性:

  • 原子性:Java內存模型保證了read、load、use、assign、store、write、lock、unlock操作具有原子性
    • 實現:原子類、synchronized
  • 可見性:當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。
    • Java內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值來實現可見性
    • 實現:volatile、synchronize、final
  • 有序性:在本線程內觀察,所有操作都是有序的。在一個線程觀察另一個線程,所有操作都是無序的,無序是因為發生了指令重排序。
    • 實現:volatile、synchronized

8.Java內存空間是怎么分配的?

  • 對象優先在Eden區分配
  • 大對象直接進入老年代
  • 長期存活的對象進入老年代
  • 動態對象年齡判定
  • 空間分配擔保

8.Full GC觸發條件

  • System.gc()
  • 老年代空間不足
  • 空間分配擔保失敗

8.類加載機制

  • 加載:1.通過類文件加載二進制字節流 2.在方法區創建類的動態存儲結構 3.在內存中創建class對象作為方法去的訪問入口。
  • 驗證:驗證class文件的字節流是否符合虛擬機要求。
  • 准備:為類變量分配內存並設置初始值。
  • 解析:將常量池的符號引用替換為直接引用的過程。
  • 初始化:執行Java程序代碼。

8.新生代和老年代可以轉換嗎?

對象優先分配在新生代的Eden區,通過長期存活(達到一定歲數)的對象進入老年代和動態對象年齡判定使對象從新生代進入老年代。

9.這些內存里面的垃圾怎么回收?
引用計數法和可達性分析法。回收算法包括:標記-清除、標記-整理、復制、分代收集算法。

10.怎么判斷是垃圾?GCRoot可以為哪些?

可達性分析法中,從GC Root出發,不可達的是可以被回收的對象。

  • Java虛擬機棧局部變量表中引用對象。
  • 本地方法棧JNI中引用的對象。
  • 方法區中類靜態變量引用的對象。
  • 方法去中常量引用的對象。

11.G1收集器

垃圾收集器都存在 Stop The World 的問題,G1對這個問題進行了優化,G1對整個新生代和老年代一起回收,把堆划分為多個大小相等的獨立區域region,使得每個region可以單獨進行垃圾回收,通過記錄每個region垃圾回收時間以及回收所獲得的空間(通過過去回收的經驗獲得),並維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的region。

初始標記 -> 並發標記 -> 最終標記 -> 篩選回收

特點:

  • 空間整合:基於標記-整理和復制,不會產生內存空間碎片
  • 可預測的停頓:也可以並發執行

8.BIO、NIO、AIO

BIO,同步阻塞IO,一個線程處理一個連接,發起和處理IO請求都是同步的

NIO,同步非阻塞IO,一個線程處理多個鏈接,發起IO請求是非阻塞的,處理IO請求是同步的(輪詢)

AIO,異步非阻塞IO,一個有效請求一個線程,發起和處理IO請求都是異步的。

9.AQS

   描述
 FutureTask  用來封裝Callable的返回值
 BlockingQueue  當隊列中沒有元素時take()被阻塞,當隊列滿時put()被阻塞
 ForkJoin  大的計算任務拆分成小任務,並行計算

10.JUC

 描述 CountDownLatch countDown()會使計數器減1,當計數器為0時,調用await()的線程會被喚醒 CyclicBarrier await()會使計數器減1,當計數器為0時,所有調用await()的方法會被喚醒 Semaphore 類似於操作系統的信號量,可以控制對互斥資源的訪問線程數

11.實現線程安全的方法

  • 不可變
  • synchronized和ReentrantLock
  • CAS、AtomicInteger
  • TreadLocal

12.IO與NIO

  I/O NIO
數據打包和傳輸方式
是否阻塞 阻塞 非阻塞

13.NIO

  • 通道(Channel):對原I/O包中的流的模擬,可以通過它讀取和寫入數據,流是單向的,通道是雙向的,可以同時用於讀、寫或者同時用於讀寫。
  • 緩沖區:不會直接對通道進行讀寫數據,而是要先經過緩沖區。
  • 選擇器(Selector):在Socket NIO用於IO復用。

14.Class.forName()怎么執行的?

15.守護線程是什么?守護線程是怎么退出的?

守護線程是在程序運行時提供后台服務的線程,不屬於程序運行中不可或缺的部分。

當程序中所有非守護線程結束時,程序也就終止,同時殺死所有的守護線程。

16.Stack與ArrayList的區別

Stack是用Vector實現的,Queue是用ArrayList實現的,所以比較Stack與ArrayList的區別就是比較這兩者之間的區別。

  • 一個先進先出,一個后進先出
  • 一個線程不安全,一個線程安全

17.HashMap的rehash過程

HashMap中使用一個技巧,和將哈希值與舊容量進行&運算,如果位上為0則在原位置,如果為1則在下邊。

18.hashcode和equals的區別

equals用來判斷實體在邏輯上是否相等,當重寫equals方法時要重寫hashcode方法。

  • 如果兩個對象通過equals判定相等,則hashcode相等。
  • hashcode相等,equals不一定相等。

19.equals和==的區別?我要比較內容呢?

  • equals:用來比較邏輯上是否相等
  • ==:用來判斷兩個對象地址是否相同,即是否是同一個對象。

20.Java代碼編譯過程

詞法分析 -> 語法分析 -> 語義分析 -> 字節碼生成

21.如何設計hash函數

22.常用的線程池

23.分段鎖

JVM

1.運行時數據區域

  程序計數器 JVM棧 本地方法棧 方法區 運行時常量池
功能 記錄正在執行的虛擬機字節碼指令的地址 棧幀用於存儲局部變量表、操作數棧、常量池引用等信息 與JVM棧類似,為本地方法服務 對象分配區域,垃圾收集的主要區域 用於存訪加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼 方法區的一部分,存放生成的字面量和符號引用
  線程私有 線程私有 線程私有 公有 公有 公有
垃圾收集 不需要 不需要 不需要 需要(垃圾回收的主要區域)

類的卸載:1.類實例被回收2.加載類的classloader被回收3.class對象沒有被引用

方法區在jdk1.8以前放在永久代中,jdk1.8以后放在本地內存中,而不是JVM內存

需要

2.垃圾收集算法

  新生代 老年代    
垃圾收集算法 復制(Eden:Survivor) 標記-清除/標記-整理    
GC Minor GC Full GC    
觸發條件 Eden空間滿時

1.調用System.gc()

2.老年代空間不足

3.空間分配擔保失敗

   
         

3.類加載過程:

  • 加載:從各種渠道獲取二進制字節流轉化為方法區的運行時存儲結構,在內存中生成一個class對象作為訪問入口。
  • 驗證:確保字節流符合當前虛擬機的要求
  • 准備:為類變量分配內存並設置初始值
  • 解析:將常量池的符號引用替換為直接引用的過程
  • 初始化:虛擬機執行類構造器clinit方法的過程。<clinit>()是由編譯器自動收集類中所有類變量的賦值動作和靜態語句塊中的語句合並產生的

4.引用類型

  描述
強引用 不會被回收
軟引用 只有在內存不夠的情況下才會被回收
弱引用 一定會被回收,只能存活到下一次垃圾回收發生之前
虛引用 不會對其生存時間造成影響,唯一目的是在這個對象被回收時收到一個系統通知

5.垃圾收集算法

  描述 不足
標記-清除 標記要收集的對象,然后清除 標記和清除效率都不高,造成內存碎片
標記-整理 讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存 對標記-清除算法的補充
復制 將內存划分為相等的兩塊,每次只使用其中的一塊,當這一塊內存用完了就將還存活的對象復制到另一塊上面,然后把使用過的內存空間進行一次清理 只使用了內存的一半
分代收集

他根據對象存活周期將內存划分為幾塊,不同塊采用適當的收集算法。

一般將堆分為新生代和老年代。

  • 新生代使用:復制算法
  • 老年代使用:標記-清除 或者 標記-整理 算法
 

6.垃圾收集器

  多線程與單線程 串行與並行 描述 適用場景
Serial收集器 單線程 串行 簡單高效 Client
ParNew收集器 多線程 串行 Serial的多線程版本 Server
Parallel Scavenge收集器 多線程 串行 動態調整以提供最合適的停頓時間或者最大吞吐量 注重吞吐量以及CPU資源敏感場合
Serial Old收集器 單線程 串行 Serial的老年代版本 Client
Parallel Old收集器 多線程 串行 Parallel Scavenge的老年代版本 注重吞吐量以及CPU資源敏感場合
CMS收集器 多線程 串行/並行

吞吐量低

無法處理浮動垃圾

標記-清除導致碎片

 
G1收集器 多線程 串行/並行

空間整合:基於 標記-整理

可預測的停頓

 

7.內存分配與回收

  描述 特點 觸發條件  
Minor GC 回收新生代 頻繁,快 Eden區空間不夠時  
Full GC 回收老年代和新生代 少,慢

1. System.gc()

2. 老年代不足(大對象、長期存活的對象進入老年代)

3. 空間分配擔保失敗

 

內存分配策略

  • 對象優先在Eden分配
  • 大對象直接進入老年代
  • 長期存活的對象進入老年代:年齡計數器
  • 動態對象年齡判定
  • 空間分配擔保

計算機網絡

1.簡述TCP的三次握手、四次揮手,為什么要三次握手?為什么client會進入TIME_WAIT?

TCP的三次握手:

三次握手過程中主要對序號(seq)、確認序號(ack)、標志位(ACK、SYN)進行操作。

(1)client端發送連接請求:SYN=1(建立新連接),seq=x

(2)server端接收請求並返回確認報文:SYN=1(建立新連接),ACK=1(ack有效),ack=x+1,seq=y

(3)client接收到確認報文,再次發送確認消息:ACK=1(ack有效),seq=x+1(client上一條請求seq+1),ack=y+1

(4)server端收到確認后,連接建立

TCP的四次揮手:

(1)client端發送連接釋放報文:FIN=1,seq=u

(2)server收到之后發出確認,此時TCP屬於半關閉狀態,server能向client發送數據反之不能:ACK=1,seq=v ack=u+1

(3)當server處理完畢后,發送連接釋放報文:FIN=1,ACK=1,seq=w,ack=u+1

(4)client收到后發出確認,進入TIME-WAIT狀態,等來2MSL(最大報文存活時間)后釋放連接:ACK=1,seq=u+1,ack=w+1

(5)server收到client的確認后釋放連接

為什么要進行三次握手?

第三次握手時為了防止失效的連接請求到達服務器,讓服務器錯誤打開連接。

客戶端發送的連接請求如果在網絡中滯留,那么就會隔很長時間才能收到服務器的連接確認。客戶端等待一個超時重傳時間后,就會重新發起請求。但是這個滯留的連接請求最后還是會到達服務器,如果不進行第三次握手,那么服務器就會打開兩個連接。如果有第三次握手,客戶端會忽略服務器發送的對滯留連接請求的連接確認,不進行第三次握手,因此就不會再次打開連接。

為什么會有TIME_WAIT?

客戶端接收到服務器的FIN報文后進入TIME_WAIT狀態而不是CLOSED,還需要等待2MSL,理由:

確保最后一個確認報文能夠到達。如果server端沒收到client端發來的確認報文,那么就會重新發送連接釋放請求報文。

為了讓本連接持續時間內所產生的所有報文都從網絡中消失,使得下一個新的連接不會出現舊的連接請求報文。

2.TCP的擁塞控制

慢開始:最初,發送方只能發送一個報文段(假設),當收到確認后,將擁塞窗口(cwnd)加倍,呈指數型增長

擁塞避免:設置一個慢開始門限ssthresh,當cwnd>=ssthresh,進入擁塞避免,每個輪次只將cwnd加1

快重傳:在接收方,要求每次接收到報文段都應該對最后一個已收到的有序報文段進行確認。例如已經接收到M1和M2,此時收到M4,應該發送對M2的確認。在發送方,如果收到三個重復確認,那么可以知道下一個報文段丟失,此時執行快重傳,立即重傳下一個報文段。

快恢復:在這種情況下,只是丟失個別報文段,不是網絡擁塞,因此執行快恢復,令ssthresh=cwnd/2,cwnd=ssthresh,此時直接進入擁塞避免。

3.瀏覽器輸入url請求服務器的過程,分析其中哪些部分用到緩存。

輸入url

瀏覽器查找瀏覽器緩存

若瀏覽器緩存中未找到,查找本機host文件

若本機host文件中未找到,則查找路由器、ISP緩存

若路由器、ISP緩存中未找到,則向配置的DNS服務器發起請求查找(若本地域名服務器未找到,會向根域名服務器->頂級域名服務器->主域名服務器)

獲取到url對應的ip后,發起TCP三次握手

發送http請求,將響應顯示在瀏覽器頁面中

四次揮手結束

4.ARP(地址解析協議)

ARP實現由IP地址得到MAC地址。

主機A知道主機B的IP地址,但是ARP高速緩存中沒有該IP地址到MAC地址的映射,此時主機A通過廣播的方式發送ARP請求分組,主機B收到該請求后會發送ARP響應分組給主機A告知其MAC地址,隨后主機A向其高速緩存中寫入主機B的IP地址到MAC地址的映射。

5.HTTP的流量控制,具體的控制算法

流量控制是為了控制發送方發送速率,保證接收方來得及接收。

接收方發送的確認報文中的窗口字段可以用來控制發送方窗口大小,從而影響發送方的發送速率。

6.計算機網絡體系結構

  設備 地址 通信方式 數據單位 協議 描述
應用層       報文 HTTP、DNS、FTP、DHCP、SMTP(郵件發送)、POP3和IMAP(郵件接收) 為特定應用程序提供數據傳輸服務
傳輸層       報文段

TCP

 

為進程提供數據傳輸服務
   

用戶數據報

UDP

 

網絡層

路由器(路由選擇和分組轉發)

路由協議選擇:RIP/OSPF(內部)

BGP(外部)

IP地址   分組

IP協議(分類、子網划分、無分類)

NAT:將本地IP轉換為全球IP

為主機提供數據傳輸服務

地址解析協議(ARP):由IP地址得到MAC地址

 

網際控制報文協議(ICMP):封裝在IP數據包中,但不屬於高層協議,是為了更有效地轉發IP數據包和提高交付成功的機會。

 

網際組管理協議(IGMP)

 
數據鏈路層 交換機(自學習交換表:MAC地址到接口的映射) 一台主機有多少個網絡適配器就有多少個MAC地址 廣播信道(星型、環形、直線型) 信道復用技術

頻分復用

時分復用

統計時分復用

波分復用

碼分復用

為同一鏈路的主機提供數據傳輸服務
點對點信道 CSMA/CD  
物理層 集線器  

單工通信

半雙工通信

全雙工通信

比特   在傳輸媒體上傳輸數據比特流

在向下的過程中,需要添加下層協議所需要的首部或者尾部,而在向上的過程中不斷拆開首部和尾部。

7.路由選擇協議

 

RIP

OSPF BGP
名稱   開放最短路徑優先 邊界網關協議
使用范圍 內部 內部 外部
描述 基於距離向量的路由選擇協議 洪泛法 每個自治系統必須配置BGP發言人,發言人之間通過TCP連接來交換路由信息
特點

實現簡單開銷小

最大距離為15,限制了網絡規模

故障傳播時間長

更新過程收斂快 只能尋找一條比較好的路由,而不是最佳路由

8.UDP和TCP比較

 

  UDP TCP
連接 無連接 面向連接
可靠 盡最大能力交付 可靠交付
擁塞控制
面向 面向報文 面向字節流
通信 一對一、一對多、多對一和多對多 一對一,全雙工通信

HTTP

1.HTTP的過程

類似於瀏覽器輸入url請求服務器的過程?

2.HTTPS怎么建立請求

HTTPS = HTTP + SSL(Secure Sockets Layer, 安全套接字層)

HTTPS 可以防竊聽(非對稱密鑰加密)、防偽裝、防篡改(加密和認證)

客戶端發送請求到服務器端

服務器端返回證書和公開密鑰,公開密鑰作為證書的一部分而存在

客戶端驗證證書和公開密鑰的有效性,如果有效,則生成共享密鑰並使用公開密鑰加密發送到服務器端

服務器端使用私有密鑰解密數據,並使用收到的共享密鑰加密數據,發送到客戶端

客戶端使用共享密鑰解密數據

SSL加密建立...

 3.GET和POST比較

  GET POST
作用 獲取資源 傳輸實體
參數 查詢字符串 request body
安全(不會改變服務器狀態) 安全 不安全
冪等性 滿足 不滿足
緩存 可緩存 多數情況下不可以

 

MySQL

1.mysql的索引,最左匹配原則

索引可以加快對數據的檢索。常見的有B+Tree索引,哈希索引。

最左匹配原則:

當索引是聯合索引,在查詢條件中,mysql是從最左邊開始命中的,如果出現了范圍查詢(>、<、between、like),就不能進一步命中了,后續退化為線性查找,列的排列順序決定了可命中索引的列數。

2.mysql的主從復制

mysql為了保持高可用,會采用一主多從的結構,一個master節點,多個slave節點,master節點可以進行寫操作,而slave節點只能進行讀操作。

binlog線程:將主服務器上的數據更改寫入二進制日志中

I/O線程:從主服務器上讀取二進制日志,並寫入從服務器的重放日志中

SQL線程:讀取重放日志並重放其中的SQL語句

3.mysql的聚集索引、非聚集索引

聚集索引:以主鍵創建的索引,在葉子結點上存儲的是表中的數據

非聚集索引:以非主鍵創建的索引,葉子結點上存儲的是主鍵和索引列

使用非聚集索引查詢出數據時,拿到葉子上的主鍵再去查到想要查找的數據。(回表)

4.mysql聯合索引,要注意什么?

聯合索引即索引由多個列(a,b,c,d)組成,要注意索引的命中,最左匹配原則,從左開始命中,遇到范圍查詢就不能進一步匹配。

5.為什么數據庫要使用B+樹來實現索引?

更少的查找次數(B+樹相比紅黑樹更矮胖)

利用磁盤預讀特性(一次IO能完全載入一個節點)

6.MySQL索引

  描述 特點 使用場景
B+ Tree索引

使用B+ Tree作為底層實現

對樹進行搜索,查找速度快

分為聚簇索引和非聚簇索引

查找、排序、分組
哈希索引

使用哈希作為底層實現

無法用於排序與分組

只支持精確查找,時間復雜度為O(1)

當索引值使用的頻繁時,會在B+ Tree索引之上再創建一個哈希索引
全文索引 全文索引使用倒排索引實現,記錄着關鍵詞到其所在文檔的映射   查找文本中的關鍵詞
空間數據索引   從所有維度來索引數據 用於地理數據存儲

索引優化:

  • 獨立的列:索引列不能是表達式的一部分,也不能是函數的參數。
  • 多列索引:多個列為條件查詢時,使用多列索引。
  • 索引的順序:讓選擇性最強的索引放在最前面。
  • 前綴索引:對於BLOB、TEXT、VARCHAR類型的列,必須使用前綴索引,只索引開始的部分字符。
  • 覆蓋索引:索引包含所有需要查詢的字段的值。

索引的優點:

  • 大大減小了服務器需要掃描的行數。
  • 幫助服務器避免排序和分組。
  • 將隨機I/O變為順序I/O。

7.InnoDB和MyISAM比較

  InnoDB MyISAM
默認
隔離級別 四個隔離級別  
事務 支持 不支持
行級/表級 表級
外鍵 支持 不支持
備份 在線熱備份  
崩潰恢復   概率高,恢復慢
特性   壓縮表和空間數據索引
使用場景   讀寫分離的讀表

8.切分

  • 水平切分:將同一個表中的記錄拆分到多個結構相同的表中。
    • 切分策略:
      • 哈希取模
      • 范圍:ID或者時間
      • 映射表:使用單獨的一個數據庫來存儲映射關系
  • 垂直切分:將一個表按列切分成多個表,通常按關系緊密度或者使用頻率來切分。

9.MySQL數據庫是怎么插入的?

10.事務怎么回滾?里面有什么日志?

11.一百萬條數據記錄,如何分頁顯示最后一條?

設一個列從1開始自增,並設為索引,以這個列為條件進行查詢。

12.數據庫事務隔離級別,可重復度和可串行化實現的原理

隔離級別:讀未提交、讀已提交、可重復度、串行化

  • 可重復度:MVCC(多版本並發控制)
  • 串行化:MVCC + Next-Key Locks(Record Locks(鎖定索引) + Gap Locks(鎖定索引間的間隙))

數據庫

1.數據庫並發一致性問題

數據庫並發一致性問題是由於隔離性導致的。

  • 丟失修改:新的修改覆蓋了老的修改。
  • 讀臟數據:讀取其他線程rollback了的數據。
  • 不可重復讀:數據的值被修改。
  • 幻影讀:整條數據的插入和刪除。

2.封鎖

  • 封鎖粒度:表級鎖 行級鎖
  • 封鎖類型:讀寫鎖 意向鎖
  • 封鎖協議:三級封鎖協議 兩段鎖協議

3.多版本並發控制

  基於 描述
系統版本號 系統 沒開始一個事務,系統版本號+1
事務版本號 事務 事務開始時的系統版本號
創建版本號 行數據 數據創建時的系統版本號
刪除版本號 行數據 數據刪除時的系統版本號

4.異常和數據庫范式

  描述
1NF 屬性不可分
2NF 每個非主屬性完全依賴於鍵碼
3NF 每個非主屬性不傳遞函數依賴於鍵碼

5.連接

  關鍵字 描述
內鏈接 INNER JOIN 等值連接
自連接 INNER JOIN 自己連接自己
自然連接 MATURAL JOIN 所有同名列的等值連接
外連接 LEFT OUTER JOIN 保留左表沒有關聯的行
RIGHT OUTER JOIN 保留右表沒有關聯的行
OUTER JOIN 保留所有沒有關聯的行

數據結構

1.B+樹和B樹的區別

B+樹的數據都在葉子結點上,而B樹的非根非葉節點也是數據節點,所以B+樹的查詢更穩定。

B+樹有兩個指針,一個指向root節點,一個指向葉子節點的最左側,因此其范圍查詢效率更高,而B樹則需要中序遍歷B樹。

同階的情況下,B+樹節點中的關鍵字要比B樹多一個,並且B+樹的中間節點不保存數據,所以磁盤也能夠容納更多結點元素,因此B+樹更加矮胖,查詢效率也更高。

2.紅黑樹

紅黑樹是一個自平衡二叉查找樹。時間復雜度O(log n)

  • 節點顏色為紅或者黑
  • 根結點是黑色
  • 葉節點(NIL結點,空結點)為黑
  • 紅節點的孩子為黑(路徑上不能有兩個連續的紅節點)
  • 從根到葉子節點路徑中的黑節點數相等

3.紅黑樹和平衡二叉樹的區別

平衡二叉樹和高度相關,保持平衡的代價更高(多次旋轉),因此適用於插入、刪除較少,查詢較多的場景。

紅黑樹和高度無關,旋轉次數較少,因此適用於插入、刪除較多的場景。

框架

1.Mybatis動態代理

2.Spring IOC是什么?怎么實現的?

3.Spring IOC里面的反射機制怎么實現的?

Redis

1.redis分片,客戶端請求怎么處理?

  Redis的分片是指將數據分散到多個Redis實例中的方法,分片之后,每個redis擁有一部分原數據集的子集。在數據量非常大時,分片能將數據量分散到若干主機的redis實例上,進而減輕單台redis實例的壓力。

  • 范圍分片
  • 哈希分片

分片的位置:

  • 客戶端分片
  • 代理分片
  • 服務器分片

 2.redis的zset底層實現

  跳躍表來實現。

  跳躍表相比於紅黑樹的優點:

  • 存取速度快,節點不需要進行旋轉
  • 易於實現
  • 支持無鎖操作

3.redis和mysql的區別

  • redis是key-value非關系型數據庫,MySQL是關系型數據庫
  • redis基於內存,也可持久化,MySQL是基於磁盤
  • redis讀寫比MySQL快的多
  • redis一般用於熱點數據的緩存,MySQL是存儲

4.redis加鎖

redis為單進程單線程模式,采用隊列模式將並發訪問變為串行訪問,redis本身沒有鎖的概念,但可以用redis實現分布式鎖。

  • INCR
Redis Incr 命令將 key 中儲存的數字值增一。
如果 key 不存在,那么 key 的值會先被初始化為 0 ,然后再執行 INCR 操作。
  • SETNX
  • SET:以上兩種都沒有設置超時時間,SET可以實現超時時間

分布式鎖的核心思想是將設置鎖和超時時間、刪除鎖分別作為一個原子操作進行。

5.redis的淘汰策略

  • volatile-lru:在設置超時時間的數據中進行lru
  • volatile-ttl:在設置超時時間的數據中挑選即將過期
  • volatile-random:在設置超時時間的數據中隨機挑選
  • allkeys-lru:所有數據的lru
  • allkeys-random:所有數據的隨機
  • noeviction:禁止驅逐數據

6.redis無法被命中怎么辦?會出現什么問題?

無法被命中:無法直接通過緩存得到想要的數據

解決方案:

  • 緩存盡可能聚焦在高頻訪問且時效性不高的業務熱點上。
  • 將緩存容量設置為熱點數據的容量。
  • 緩存預加載。
  • 設置合適的緩存淘汰策略。

7.Redis和MySQL復制和分片

  復制 分片
MySQL 三個線程(binlog線程、I/O線程、SQL線程),目的是實現讀寫分離 水平切分、垂直切分
Redis 使用RDB快照進行復制,發送期間使用緩沖區記錄執行的寫命令,在RDB快照發送完畢后,發送緩沖區中的寫命令 水平切分

 8.Redis是什么?Sorted List是什么?skiplist是什么?怎么實現的?怎么插入一個值?怎么進行查詢?和其他數據結構進行對比?

9.Redis的hash和Java的map的區別

消息隊列

JVM

1.四種引用類型

  • 強引用:如用new關鍵字創建,不會進行回收。
  • 軟引用:在內存不足的情況下會進行回收。
  • 弱引用:只能存活到下一次垃圾回收。
  • 虛引用:不影響其生存周期,只是在回收的時候收到一個系統通知。

2.可達性分析算法的root

可達性分析算法是從GC root出發,只有通過GC root可達的對象是被引用的對象,不可達的對象屬於可以回收的對象。

操作系統

1.進程和線程的區別

  • 擁有資源:進程是資源分配的基本單位,但是線程不擁有資源,線程可以訪問隸屬進程的資源。
  • 調度:線程是獨立調度的基本單位,在同一進程中,線程的切換不會引起進程切換,從一個進程中的線程切換到另一個進程中的線程,會引起進程切換。
  • 系統開銷:創建、撤銷或切換進程,系統都要為之分配、回收資源或保存環境,開銷遠比線程大。
  • 通信方面:線程間可以通過直接讀取統一進程中的數據進行通信,但是進程通信需要借助IPC。

主線程是什么?

2.操作系統的內存管理

  • 分頁地址映射:分頁是一種動態重定位技術,通過頁表將邏輯地址映射為物理地址。
  • 段式存儲:分頁有一個不可避免的問題就是用戶視角的內存和實際物理內存的分離,分段則是將邏輯地址空間分成一組段,每個段長度可以不同,並且可以動態增長,由段表來維護。
  • 段頁式存儲:段內分頁。

3.分頁式的頁表放在哪

進程控制塊(PCB)中。

4.進程的PCB里還有哪些東西?

  • 進程狀態
  • 程序計數器
  • CPU寄存器
  • CPU調度信息
  • 內存管理信息
  • 記賬信息
  • I/O狀態信息

5.MMU(內存管理單元)

內存管理單元(MMU)管理着地址空間和物理內存的轉換,根據其內存管理方式的不同,其中包括基地址寄存器、界限地址寄存器的值以及段表和頁表。

6.進程通信

  • 管道(父子進程間通信)
  • 命名管道FIFO(去除管道只能在父子進程間進行通信,常用於客戶-服務器應用程序中)
  • 信號量
  • 消息隊列
  • 共享內存(生產者消費者的緩沖池)
  • 套接字(可用於不同機器間的進程通信)

7.共享內存

采用共享內存的進程間通信需要通信進程建立共享內存區域。通常一塊共享內存區域駐留在生成共享內存段的進程的地址空間。需要使用信號量用來同步對通向存儲的訪問。

8.Inode

9.應用程序是如何讀取文件的?

LINUX

1.linux腳本,殺掉包含一個關鍵字的所有進程

ps -ef | grep 關鍵字 | awk '{print $2}' | xargs kill -9

2.自旋鎖和互斥鎖

都屬於linux內核中的內核鎖。

互斥鎖通過對共享資源的鎖定和互斥解決利用資源沖突問題,互斥鎖是選擇睡眠的方式來對共享工作停止訪問的。

自旋鎖不會引起調度者睡眠,而是一直循環。

Socket

1.linux I/O模型,說說select和epoll的區別

  • Socket等待數據到達
  • 復制到內核緩沖區中
  • 從內核緩沖區復制到應用進程緩沖區中
  描述 特點
阻塞式I/O 應用進程被阻塞,知道數據從內核緩沖區復制到應用進程緩沖區才返回 阻塞期間,其他進程繼續執行,CPU利用率高
非阻塞式I/O 輪詢I/O是否完成 多次執行系統調用,CPU利用率低
I/O復用 select poll epoll 單個線程具有處理多個I/O時間的能力
信號驅動I/O 執行系統調用后立即返回,內核在數據到達時向應用進程發送SIGIO信號,應用進程收到后將數據從內核復制到應用進程 CPU利用率比非阻塞I/O高
異步I/O 系統調用立即返回,不會阻塞,內核完成所有操作后向應用進程發送信號

異步I/O通知應用進程I/O完成

信號驅動I/O是通知應用進程可以開始I/O

 

  select poll epoll
timeout精度 1ns 1ms 1ms
描述符數據結構 數組(因此有最大限制) 鏈表 鏈表
復制描述符 每次調用都需要復制 每次調用都需要復制 第一次復制、修改
返回結果 不返回准備好的描述符 不返回准備好的描述符 准備好的描述符加入到一個鏈表中管理
支持 幾乎所有系統 較新的系統 Linux系統
適用場景 實時性高,所有平台 實時性低,描述符適中 描述符多,描述符變化小

select和epoll的區別

  • select、poll每次調用都要把fd集合從用戶態往內核態拷貝一次,並且要把當前進程往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把當前進程往等待隊列上掛也只掛一次(在epoll_waitd的開始,注意這里的等待隊列並不是設備等待隊列,只是一個epoll內部定義的等待隊列)。
  • select、poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll是在設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進行睡眠的進程。select和poll要遍歷整個fd集合,epoll只要判斷一下就緒鏈表是否為空就行了。

2.多路復用模型

分布式

1.分布式事務

CAP定理:

  • 一致性(Consistency):多個數據副本是否能保持一致的特性。
  • 可用性(Availability):分布式系統在面對各種異常時可以提供正常服務的能力。
  • 分區容忍性(Partition tolerance):分布式系統在遇到任何網絡分區故障的時候,仍然需要能對外提供一致性和可用性的服務。

在分布式系統中,分區容忍性必不可少,因為需要總是假設網絡是不可靠的。因此,CAP理論實際上是要在可用性和一致性做權衡。

BASE:

BASE是基本可用(Basically Available)、軟狀態(Soft State)和最終一致性(Eventually Consistent)三個短語的縮寫。

BASE理論是對CAP中一致性和可用性權衡的結果,它的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,采用適當的方式來使系統達到最終一致性。

ACID要求強一致性,通常運用在傳統的數據庫系統上。而BASE要求最終一致性,通過犧牲強一致性來達到可用性,通常運用於大型分布式系統中。

解決方案:

(1)兩階段提交(2PC)

基於XA協議實現

存在問題:1.同步阻塞 2.單點問題 3.數據不一致 4.太過保守

(2)TCC

針對每個操作,都要注冊一個與其對應的確認和補償操作

Try/Confirm/Cancel

(3)本地消息表(異步確保)

將業務操作和本地消息表放在一個事務中。業界使用最多。

(4)Sagas事務模型

事件溯源,相當於將分布式系統中的操作都記錄到數據庫日志表中,要獲得最新的狀態,則需要重新執行一遍日志表的操作。並且可以dump某一時刻的數據表,在此基礎上執行在這之后的操作。

手寫代碼

1.二叉樹的先序遍歷,層序遍歷的實現

private static class Node{
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }
}

/**
 * 先序遍歷非遞歸版
 * @param root
 */
public void preOrder1(Node root){
    if(root == null) return;
    Stack<Node> stack = new Stack<>();
    stack.push(root);
    while(!stack.isEmpty()){
        Node node = stack.pop();
        System.out.println(node.value);
        if(node.right != null) stack.push(node.right);
        if(node.left != null) stack.push(node.left);
    }
}

/**
 * 先序遍歷非遞歸版回溯法
 * @param root
 */
public void preOrderBack(Node root){
    if(root == null) return;
    Stack<Node> stack = new Stack<>();
    while(root != null || !stack.isEmpty()){
        if(root != null){
            System.out.println(root.value);
            stack.push(root);
            root = root.left;
        }else{
            Node upNode = stack.pop();
            root = upNode.right;
        }
    }
}
  public void preOrderTrans(Node root){
        if(root == null) return;
        System.out.println(root.value);
        preOrderTrans(root.left);
        preOrderTrans(root.right);
    }

    public void bfs(Node root){
        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            Node top = queue.remove();
            System.out.println(top.value);
            if(top.left != null) queue.add(top.left);
            if(top.right != null) queue.add(top.right);
        }
    }
View Code

2.用棧實現隊列

import java.util.Stack;

public class Queue {
    private Stack<Integer> stack1 = new Stack<>();
    private Stack<Integer> stack2 = new Stack<>();

    public void push(Integer value){
        if(value == null){
            throw new RuntimeException("value is null");
        }
        stack1.push(value);
    }
    public Integer pop(){
        if(stack1.size() == 0){
            return null;
        }
        while(!stack1.empty()){
            stack2.push(stack1.pop());
        }
        Integer value = stack2.pop();
        while(!stack2.empty()){
            stack1.push(stack2.pop());
        }
        return value;
    }

    public static void main(String[] args) {
        Queue queue = new Queue();
        queue.push(1);
        queue.push(3);
        queue.push(5);
        queue.pop();
        queue.pop();
        queue.pop();
    }
}
View Code

3.包括max函數的棧

import java.util.Stack;

public class StackMax {

    private Stack<Integer> stack = new Stack<>();
    private Stack<Integer> s = new Stack<>();

    public void push(Integer value) {
        stack.push(value);
        if (s.size() == 0 || value >= s.peek()) {
            s.push(value);
        }
    }

    public Integer pop() {
        Integer value = stack.pop();
        if(value.equals(s.peek())){
            s.pop();
        }
        return value;
    }

    public Integer max() {
        return s.peek();
    }

    public static void main(String[] args) {
        StackMax stackMax = new StackMax();
        stackMax.push(1);
        System.out.println(stackMax.max());
        stackMax.push(3);
        System.out.println(stackMax.max());
        stackMax.push(4);
        System.out.println(stackMax.max());
        stackMax.push(2);
        System.out.println(stackMax.max());
        stackMax.pop();
        System.out.println(stackMax.max());
        stackMax.pop();
        System.out.println(stackMax.max());
        stackMax.pop();
        System.out.println(stackMax.max());
    }
}
View Code

4.找一個n*n矩陣的最長上升序列

5.快速排序,什么時候復雜度最大

  public void quickSort(int[] num, int st, int ed) {
        if (st >= ed) return;
        int left = st;
        int right = ed;
        int value = num[left];
        while (left < right) {
            while(left < right && num[right] >= value){
                right--;
            }
            num[left] = num[right];
            while(left < right && num[left] < value){
                left++;
            }
            num[right] = num[left];
        }
        int mid = left;
        num[mid] = value;
        quickSort(num, st, mid - 1);
        quickSort(num, mid + 1, ed);
    }

    public static void main(String[] args) {
        QuickSort quickSort = new QuickSort();
        int[] num = {3, 7, 4, 2, 5, 8, 1};
        quickSort.quickSort(num, 0, 6);
        for (int t : num) {
            System.out.println(t);
        }
    }
View Code

6.歸並排序

import java.util.Arrays;

public class MergeSort {

    public int[] mergeSort(int[] num) {
        if (num.length <= 1) return num;
        int mid = num.length / 2;
        int[] left = Arrays.copyOfRange(num, 0, mid);
        int[] right = Arrays.copyOfRange(num, mid, num.length);
        return mergeArrays(mergeSort(left), mergeSort(right));
    }

    private int[] mergeArrays(int[] mergeSort1, int[] mergeSort2) {
        int[] result = new int[mergeSort1.length + mergeSort2.length];
        int i = 0, j = 0, k = 0;
        while (i < mergeSort1.length && j < mergeSort2.length) {
            if (mergeSort1[i] < mergeSort2[j]) {
                result[k++] = mergeSort1[i++];
            } else {
                result[k++] = mergeSort2[j++];
            }
        }
        while (i < mergeSort1.length) {
            result[k++] = mergeSort1[i++];
        }
        while (j < mergeSort2.length) {
            result[k++] = mergeSort2[j++];
        }
        return result;
    }

    public static void main(String[] args) {
        MergeSort mergeSort = new MergeSort();
        int[] num = {3, 7, 4, 2, 5, 8, 1};
        num = mergeSort.mergeSort(num);
        for (int t : num) {
            System.out.println(t);
        }
    }
}
View Code

7.手寫一個LRU

8.給你一個數組,數組長度為n。請找出數組中第k大的數

public class Solution {
    public int findK(int[] num, int k) {
        return quickSort(num, 0, num.length - 1, k - 1);
    }

    public int quickSort(int[] num, int st, int ed, int k) {
        if (st >= ed) return num[st];
        int value = num[st];
        int left = st;
        int right = ed;
        while (left < right) {
            while (left < right && num[right] >= value) {
                right--;
            }
            num[left] = num[right];
            while (left < right && num[left] < value) {
                left++;
            }
            num[right] = num[left];
        }
        num[left] = value;
        if (left == k) return value;
        else if (left < k) {
            return quickSort(num, left + 1, ed, k);
        } else {
            return quickSort(num, st, left, k);
        }
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] num = {1,8,8,7,4,1,5,1,5,7};
        System.out.println(solution.findK(num, 1));
        System.out.println(solution.findK(num, 2));
        System.out.println(solution.findK(num, 3));
        System.out.println(solution.findK(num, 4));
        System.out.println(solution.findK(num, 5));
        System.out.println(solution.findK(num, 6));
        System.out.println(solution.findK(num, 7));
        System.out.println(solution.findK(num, 8));
        System.out.println(solution.findK(num, 9));
        System.out.println(solution.findK(num, 10));
    }
}
View Code

附加條件:不允許改變元素在數組中的位置

在int范圍內去中位數,算出其在數組中是第幾大的元素(遍歷數組O(n)),與k比較不斷二分。

 9.找到數據流中的中位數

使用大小頂堆,如果放入的是奇數個,則取大頂堆的根結點,如果是偶數個則取大小頂堆根結點的平均值。

  • 如果是奇數,放入小頂堆,然后取根結點加入大頂堆。
  • 如果是偶數,放入大頂堆,然后取根結點加入小頂堆。

10.刪除鏈表中重復節點

HashSet

11.給定一個排序鏈表,刪除所有重復的元素,使得每個元素只出現一次。

12.給定過一個二叉樹,原地將它展開為鏈表

13.給定一個二叉樹,想象自己站在他的右側,按照從頂部到底部的順序,返回從右側所能看到的節點值。

11.判斷是否是二叉搜索樹

中序遍歷

12.合並兩個鏈表,用遞歸和非遞歸實現

13.字符串是否為給定字符串的子串

14.查找兩個鏈表的公共節點

15.小頂堆

16.一個數x,一個數n,x中刪n位使得剩下的數最大

17.給定一顆二叉樹,求其中root的最長路徑。所謂路徑是指,聯通兩個節點的最小邊數。

18.二叉樹的序列化與反序列化


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM