2022-02-14
面向面試編程連載(二)_全球時快訊
Spring 依賴注入概念和@Autowired 的用法。
概念:實例不再由程序員實例化,而是通過spring容器幫我們new指定實例并且將實例注入到需要該對象的類。依賴注入能夠讓相互協(xié)作的軟件組件保持松散耦合@Autowired 注釋,它可以對類成員變量、方法及構造函數(shù)進行標注,完成自動裝配的工作。 通過 @Autowired的使用來消除 set ,get方法。也可作用與集合上這里授權服務配置類是繼承了AuthorizationServerConfigurerAdapter,而AuthorizationServerConfigurerAdapter又實現(xiàn)了AuthorizationServerConfigurer接口!源碼AuthorizationServerConfigurer@Autowired對List自動注入//@Autowired注解用在接口的集合上面,所有實現(xiàn)該接口的實現(xiàn)類都會在該集合中@Autowired(required = false)private List tasks = Collections.emptyList();
Spring Bean 的生命周期。
Bean 的生命周期概括起來就是 4 個階段:
實例化(Instantiation)
屬性賦值(Populate)
【資料圖】
初始化(Initialization)
銷毀(Destruction)
Spring Boot 啟動流程以及底層源碼
索引的數(shù)據(jù)結構(比如 B+樹)
B+TreeB+Tree相對于B-Tree有幾點不同:非葉子節(jié)點只存儲鍵值信息。所有葉子節(jié)點之間都有一個鏈指針。數(shù)據(jù)記錄都存放在葉子節(jié)點中。查詢速度快,但是占用空間索引結構:B-Tree B+Tree B:balanceB-Tree:平衡二叉樹特點:1.具有數(shù)據(jù)節(jié)點2.指向下層指針3.指向數(shù)據(jù)指針缺頁查詢,產生IOB+Tree:特點:1.具有數(shù)據(jù)節(jié)點2.指向下層指針命中數(shù)據(jù)3層查找后查詢數(shù)據(jù)指針加載更快,產生更少IO效率:BTree更高,但從IO角度,Mysql選擇B+TreeHash 索引的特點Hash 索引只能夠用于使用 = 或者 <=> 運算符的相等比較(但是速度更快)。Hash 索引不能夠用于諸如 < 等用于查找一個范圍值的比較運算符。依賴于這種單值查找的系統(tǒng)被稱為 “鍵-值存儲”;對于這種系統(tǒng),盡可能地使用 hash 索引。優(yōu)化器不能夠使用 hash 索引來加速 ORDER BY 操作。這種類型的索引不能夠用于按照順序查找下一個條目。MySql 無法使用 hash 索引估計兩個值之間有多少行(這種情況由范圍優(yōu)化器來決定使用哪個索引)。如果你將一張 MyISAM 或 InnoDB 表轉換成一個 hash 索引的內存表時,一些查詢可能會受此影響。查找某行記錄必須進行全鍵匹配。而 B-tree 索引,任何該鍵的左前綴都可用以查找記錄
索引是為了加速對表中數(shù)據(jù)行的檢索而創(chuàng)建的一種分散的存儲結構
建索引的語句
CREATE INDEX idx_xxx USING BTREE ON tablename (字段,字段,字段);
索引的種類尤其是復合索引以及對應的回表和最左匹配原則
普通索引:最基本的索引,沒有任何約束限制。唯一索引:和普通索引類似,但是具有唯一性約束,可以有 null主鍵索引:特殊的唯一索引,不允許有 null,一張表最多一個主鍵索引組合索引:多列值組成一個索引,用于組合搜索,效率大于索引合并全文索引:對文本的內容進行分詞、搜索覆蓋索引:查詢列要被所建的索引覆蓋,不必讀取數(shù)據(jù)行1、復合索引綁定的第一個列,沒有出現(xiàn)在查詢條件中;舉例說明:為emp表插入索引idx_age_deptid_name(age,deptid,name),但是在查詢條件中未使用age,導致復合索引全部失效。2、復合索引綁定的多個列是有順序的,某一個列沒有出現(xiàn)在查詢條件中,存儲引擎不能使用索引中該列及其后的所有列。舉例:為emp表插入索引idx_age_deptid_name(age,deptid,name),查詢時查詢條件里沒有deptid列,會導致復合索引中的deptid及其后的索引失效。3.查詢條件中出現(xiàn)某個列是范圍查詢的,存儲引擎不能使用復合索引中該列其后的所有列。舉例:為emp表插入索引idx_age_deptid_name(age,deptid,name),查詢時查詢條件里deptid列使用到了范圍查詢,會導致復合索引中的deptid其后的索引失效。4.查詢條件中某列使用否定條件的(!= <> IS NOT NULL),存儲引擎不能使用索引中該列其后的所有列。舉例:為emp表插入索引idx_age_deptid_name(age,deptid,name),查詢時查詢條件里deptid列使用到了否定條件,會導致復合索引中的deptid其后的索引失效。5.查詢條件中某列使用LIKE條件后的字段是以%開頭的(如:’%ABC’),存儲引擎不能使用索引中該列及其后的所有列。舉例:為emp表插入索引idx_age_deptid_name(age,deptid,name),查詢時查詢條件里name列使用到了like ‘%a’,會導致復合索引中的name及其后的索引失效。6.查詢條件中某列使用函數(shù)的,存儲引擎不能使用索引中該列及其后的所有列。舉例:為emp表插入索引idx_age_deptid_name(age,deptid,name),查詢時查詢條件里name列使用到了like ‘%a’,會導致復合索引中的name及其后的索引失效。7.查詢條件中某列使用類型轉換的(包括顯示的和隱示的),存儲引擎不能使用索引中該列及其后的所有列。如:字符串類型的列NAME=3,就是隱示的類型轉換,將INT型轉換為字符串類型。如果寫為NAME=’3’,就不是類型轉換。舉例:為emp表插入索引idx_age_deptid_name(age,deptid,name),查詢時查詢條件name=3,會導致復合索引中的name及其后的索引失效。條件寫成name=‘3’,索引就不會失效。
回表
如果索引的列在 select 所需獲得的列中(因為在 mysql 中索引是根據(jù)索引列的值進行排序的,所以索引節(jié)點中存在該列中的部分值)或者根據(jù)一次索引查詢就能獲得記錄就不需要回表,如果 select 所需獲得列中有大量的非索引列,索引就需要到表中找到相應的列的信息,這就叫回表。
使用聚集索引(主鍵或第一個唯一索引)就不會回表,普通索引就會回表
索引下推優(yōu)化,
可以在索引遍歷過程中,對索引中包含的字段先做判斷,過濾掉不符合條件的記錄,減少回表字數(shù)。
最左匹配原則
帶頭大哥不能死,中間兄弟不能斷
Spring AOP 底層原理
AOP 底層是采用動態(tài)代理機制實現(xiàn)的:接口+實現(xiàn)類
如果要代理的對象,實現(xiàn)了某個接口,那么 Spring AOP 會使用 JDK Proxy,去創(chuàng)建代
理對象。
沒有實現(xiàn)接口的對象,就無法使用 JDK Proxy 去進行代理了,這時候 Spring AOP 會使用
Cglib 生成一個被代理對象的子類來作為代理。
就是由代理創(chuàng)建出一個和 impl 實現(xiàn)類平級的一個對象,但是這個對象不是一個真正的對象,
只是一個代理對象,但它可以實現(xiàn)和 impl 相同的功能,這個就是 aop 的橫向機制原理,這
樣就不需要修改源代碼。
HashMap在java1.7之前底層數(shù)據(jù)結構是數(shù)組+鏈表,1.8之后是數(shù)組+鏈表+紅黑樹,
在1.7以前的put方法采用的是頭插法,當hash碰撞次數(shù)到達8,且桶內元素到達64個的時候形成鏈表,但是在極端情況下會造成鏈表過長,效率變低,并且在rehash的時候,頭插法會造成回環(huán)鏈首尾相連,形成死鎖,在java1.8以后采用紅黑樹,除了添加效率都高,是線程不安全的,不安全示例
public class HashMapTest { public static void main(String[] args) { HashMapThread thread0 = new HashMapThread(); HashMapThread thread1 = new HashMapThread(); HashMapThread thread2 = new HashMapThread(); HashMapThread thread3 = new HashMapThread(); HashMapThread thread4 = new HashMapThread(); thread0.start(); thread1.start(); thread2.start(); thread3.start(); thread4.start(); }}class HashMapThread extends Thread { private static AtomicInteger ai = new AtomicInteger(); private static Map map = new HashMap<>(); @Override public void run() { while (ai.get() < 1000000) { map.put(ai.get(), ai.get()); ai.incrementAndGet(); } }}
JDK1.8 之前
JDK1.8 之前 HashMap 底層是 數(shù)組和鏈表 結合在一起使用也就是 鏈表散列。 ## HashMap 通過 key 的 hashCode 經過擾動函數(shù)處理過后得到 hash 值,然后通過 (n -
1) & hash 判斷當前元素存放的位置(這里的 n 指的是數(shù)組的長度),如果當前位置存在
元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,
直接覆蓋,不相同就通過拉鏈法解決沖突。
所謂擾動函數(shù)指的就是 HashMap 的 hash 方法。使用 hash 方法也就是擾動函數(shù)是為了
防止一些實現(xiàn)比較差的 hashCode() 方法 換句話說使用擾動函數(shù)之后可以減少碰撞。
JDK1.8 之后
當鏈表長度大于閾值(默認為 8)時,會首先調用 treeifyBin()方法。這個方法會根據(jù)
HashMap 數(shù)組來決定是否轉換為紅黑樹。只有當數(shù)組長度大于或者等于 64 的情況下,才會
執(zhí)行轉換紅黑樹操作,以減少搜索時間。否則,就是只是執(zhí)行 resize() 方法對數(shù)組擴容。
1.通常代替HashMap的安全由HashTable代替,但是多線程下他的put.get方法都是synchronized,效率太低,
2.Collections.synchronizedMap(),底層仍是synchronized
3.java9實現(xiàn)Collections.of()
ConcurrentHashMap 與 ConcurrentSkipListMap
ConcurrentHashMap 加鎖
ConcurrentSkipListMap 不需要加鎖,浪費空間,
4.ConcurrentHashMap
ConcurrentHashMap如何保證線程安全,在1.7以前由劃分segment分段鎖機制,共計16個并發(fā)級別,隔離級別太大,有很多空間就浪費了,太小就段內的元素過多
1.8以后是cas算法C語言寫得,無鎖算法,put添加的時候,鏈表+紅黑樹
put方法(無鎖添加)
3、HashMap 的擴容機制是怎樣的?
一般情況下,當元素數(shù)量超過閾值時便會觸發(fā)擴容。每次擴容的容量都是之前容量的 2 倍。
HashMap 的容量是有上限的,必須小于 1<<30,即 1073741824。如果容量超出了這個
數(shù),則不再增長,且閾值會被設置為 Integer.MAX_VALUE。
JDK7 中的擴容機制
空參數(shù)的構造函數(shù):以默認容量、默認負載因子、默認閾值初始化數(shù)組。內部數(shù)組是空數(shù)
組。
有參構造函數(shù):根據(jù)參數(shù)確定容量、負載因子、閾值等。
第一次 put 時會初始化數(shù)組,其容量變?yōu)椴恍∮谥付ㄈ萘康?2 的冪數(shù),然后根據(jù)負載因子
確定閾值。
如果不是第一次擴容,則 新容量=舊容量 x 2 ,新閾值=新容量 x 負載因子 。
JDK8 的擴容機制
空參數(shù)的構造函數(shù):實例化的 HashMap 默認內部數(shù)組是 null,即沒有實例化。第一次調
用 put 方法時,則會開始第一次初始化擴容,長度為 16。 ## 有參構造函數(shù):用于指定容量。會根據(jù)指定的正整數(shù)找到不小于指定容量的 2 的冪數(shù),將
這個數(shù)設置賦值給閾值(threshold)。第一次調用 put 方法時,會將閾值賦值給容量,
然后讓 閾值 = 容量 x 負載因子。 ## 如果不是第一次擴容,則容量變?yōu)樵瓉淼?2 倍,閾值也變?yōu)樵瓉淼?2 倍。(容量和閾值都
變?yōu)樵瓉淼?2 倍時,負載因子還是不變)。
此外還有幾個細節(jié)需要注意:
首次 put 時,先會觸發(fā)擴容(算是初始化),然后存入數(shù)據(jù),然后判斷是否需要擴容;
不是首次 put,則不再初始化,直接存入數(shù)據(jù),然后判斷是否需要擴容;
4、ConcurrentHashMap 的存儲結構是怎樣的?
Java7 中 ConcurrnetHashMap 使用的分段鎖,也就是每一個 Segment 上同時只有一個
線程可以操作,每一個 Segment 都是一個類似 HashMap 數(shù)組的結構,它可以擴容,它
的沖突會轉化為鏈表。但是 Segment 的個數(shù)一但初始化就不能改變,默認 Segment 的
個數(shù)是 16 個。
Java8 中的 ConcurrnetHashMap 使用的 Synchronized 鎖加 CAS 的機制。結構也由
Java7 中的 Segment 數(shù)組 + HashEntry 數(shù)組 + 鏈表 進化成了 Node 數(shù)組 + 鏈表 / 紅
黑樹,Node 是類似于一個 HashEntry 的結構。它的沖突再達到一定大小時會轉化成紅
黑樹,在沖突小于一定數(shù)量時又退回鏈表。
5、線程池大小如何設置?
CPU 密集型任務(N+1): 這種任務消耗的主要是 CPU 資源,可以將線程數(shù)設置為 N (CPU 核心數(shù))+1,比 CPU 核心數(shù)多出來的一個線程是為了防止線程偶發(fā)的缺頁中斷,
或者其它原因導致的任務暫停而帶來的影響。一旦任務暫停,CPU 就會處于空閑狀態(tài),而
在這種情況下多出來的一個線程就可以充分利用 CPU 的空閑時間。
I/O 密集型任務(2N): 這種任務應用起來,系統(tǒng)會用大部分的時間來處理 I/O 交互,而
線程在處理 I/O 的時間段內不會占用 CPU 來處理,這時就可以將 CPU 交出給其它線程
使用。因此在 I/O 密集型任務的應用中,我們可以多配置一些線程,具體的計算方法是
2N。
如何判斷是 CPU 密集任務還是 IO 密集任務?
CPU 密集型簡單理解就是利用 CPU 計算能力的任務比如你在內存中對大量數(shù)據(jù)進行排序。單
凡涉及到網絡讀取,文件讀取這類都是 IO 密集型,這類任務的特點是 CPU 計算耗費時間相
比于等待 IO 操作完成的時間來說很少,大部分時間都花在了等待 IO 操作完成上。
6、IO 密集=Ncpu*2 是怎么計算出來?
I/O 密集型任務任務應用起來,系統(tǒng)會用大部分的時間來處理 I/O 交互,而線程在處理
I/O 的時間段內不會占用 CPU 來處理,這時就可以將 CPU 交出給其它線程使用。因此在
I/O 密集型任務的應用中,我們可以多配置一些線程。例如:數(shù)據(jù)庫交互,文件上傳下
載,網絡傳輸?shù)?。IO 密集型,即該任務需要大量的 IO,即大量的阻塞,故需要多配置線
程數(shù)。
7、G1 收集器有哪些特點?
G1 的全稱是 Garbage-First,意為垃圾優(yōu)先,哪一塊的垃圾最多就優(yōu)先清理它。
G1 GC 最主要的設計目標是:將 STW 停頓的時間和分布,變成可預期且可配置的。
被視為 JDK1.7 中 HotSpot 虛擬機的一個重要進化特征。它具備一下特點:
并行與并發(fā):G1 能充分利用 CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個 CPU(CPU 或者
CPU 核心)來縮短 Stop-The-World 停頓時間。部分其他收集器原本需要停頓 Java 線程
執(zhí)行的 GC 動作,G1 收集器仍然可以通過并發(fā)的方式讓 java 程序繼續(xù)執(zhí)行。
分代收集:雖然 G1 可以不需要其他收集器配合就能獨立管理整個 GC 堆,但是還是保留
了分代的概念。
空間整合:與 CMS 的“標記-清理”算法不同,G1 從整體來看是基于“標記-整理”算法
實現(xiàn)的收集器;從局部上來看是基于“標記-復制”算法實現(xiàn)的。
可預測的停頓:這是 G1 相對于 CMS 的另一個大優(yōu)勢,降低停頓時間是 G1 和 CMS 共
同的關注點,但 G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明
確指定在一個長度為 M 毫秒的時間片段內。
G1 收集器在后臺維護了一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先選擇回收價值最大的
Region(這也就是它的名字 Garbage-First 的由來)
8、你有哪些手段來排查 OOM 的問題?
增加兩個參數(shù) -XX:+HeapDumpOnOutOfMemoryError -
XX:HeapDumpPath=/tmp/heapdump.hprof,當 OOM 發(fā)生時自動 dump 堆內存信
息到指定目錄。
同時 jstat 查看監(jiān)控 JVM 的內存和 GC 情況,先觀察問題大概出在什么區(qū)域。
使用 MAT 工具載入到 dump 文件,分析大對象的占用情況,比如 HashMap 做緩存未
清理,時間長了就會內存溢出,可以把改為弱引用。
- 精心推薦
X 關閉
X 關閉