字節跳動面試題

Java

1.ThreadLocaljava

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

2.volatile的做用和原理mysql

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

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

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

3.J.U.C中的鎖算法

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

  實現 公平鎖 等待可中斷 條件 性能
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
鏈接 無鏈接 面向鏈接
可靠 盡最大能力交付 可靠交付
擁塞控制
面向 面向報文 面向字節流
通訊 一對1、一對多、多對一和多對多 一對一,全雙工通訊

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.二叉樹的序列化與反序列化

相關文章
相關標籤/搜索